@webority/mobile-core 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -78,6 +78,19 @@ tapped notification's data as strings, including the tap that launched the app.
78
78
  settings switch its `status` and `enabled`. The library does not remember the switch: an app that
79
79
  keeps push on calls `enablePush` at each start. `setBadgeCount(count)` sets the app icon badge.
80
80
 
81
+ - `enablePush({ prompt: false })` never shows the permission prompt: a phone already granted
82
+ registers silently, and any other resolves with its status (`unknown` when the person has not
83
+ been asked yet, `denied` when refused). Call it at sign-in, and call `enablePush()` when the
84
+ person reaches a moment where asking makes sense.
85
+ - Every registered device carries `apnsEnvironment`: `Development` or `Production` on iOS, read
86
+ from the build's `aps-environment` (expo-application), so the server sends each phone to the
87
+ APNs gateway its signing accepts. It is null on Android, and on iOS when it cannot be read (the
88
+ simulator); the server then uses its own setting.
89
+ - `onReceive(handler)` calls `handler({ notificationId, title, body, data })` for each
90
+ notification that arrives while the app is open, shown or not, so the app can refresh a list or
91
+ an unread count. It returns a function that removes the handler. A notification arriving in the
92
+ background does not reach it; its tap still goes to `onOpen`.
93
+
81
94
  ### Notification buttons
82
95
 
83
96
  A push names a category; the category's buttons are registered on the phone. Register them at
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=void 0;var _axios=_interopRequireDefault(require("axios")),_index=require("../config/index.js"),_index2=require("../logger/index.js");require("../types/axios.js");var _baseUrl=require("./baseUrl.js"),_clientHeaders=require("./clientHeaders.js"),_curl=require("./curl.js"),_minAppVersion=require("./minAppVersion.js"),_multipart=require("./multipart.js"),_requestDefaults=require("./requestDefaults.js"),_responseBodyText=require("./responseBodyText.js"),_retry=require("./retry.js");function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}const publicClient=_axios.default.create({headers:{"Content-Type":"application/json",Accept:"application/json"}});(0,_baseUrl.applyConfiguredBaseUrl)(publicClient),(0,_requestDefaults.applyRequestDefaults)(publicClient),(0,_multipart.sendFormDataAsMultipart)(publicClient),(0,_minAppVersion.readMinAppVersion)(publicClient),(0,_retry.attachRetry)(publicClient),publicClient.interceptors.request.use(e=>(_index2.Logger.info(`Request: ${e.method?.toUpperCase()} ${e.url}`),(0,_index.getConfigValue)("logCurl")&&(0,_curl.logCurl)(e),e),e=>{const r=e instanceof Error?e.message:String(e);return _index2.Logger.error("Request error:",r),Promise.reject(e)}),(0,_clientHeaders.applyClientHeaders)(publicClient),publicClient.interceptors.response.use(e=>(_index2.Logger.info(`Response: ${e.status} from ${e.config.url}`),e),e=>{if(_axios.default.isAxiosError(e)){const r=e.config;if(r?.skipErrorLog)return Promise.reject(e);e.response?_index2.Logger.error(`Response error: ${(0,_responseBodyText.responseBodyText)(e.response.data,e.message)}, URL: ${r?.url}, Status: ${e.response.status}`):_index2.Logger.error(`Network error or no response from server: ${e.message}, URL: ${r?.url}`)}else{const r=e instanceof Error?e.message:String(e);_index2.Logger.error(`Unknown response error: ${r}`)}return Promise.reject(e)});var _default=exports.default=publicClient;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=void 0;var _axios=_interopRequireDefault(require("axios")),_index=require("../config/index.js"),_index2=require("../logger/index.js");require("../types/axios.js");var _baseUrl=require("./baseUrl.js"),_clientHeaders=require("./clientHeaders.js"),_curl=require("./curl.js"),_minAppVersion=require("./minAppVersion.js"),_multipart=require("./multipart.js"),_requestDefaults=require("./requestDefaults.js"),_responseBodyText=require("./responseBodyText.js"),_retry=require("./retry.js");function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}const publicClient=_axios.default.create({headers:{"Content-Type":"application/json",Accept:"application/json"}});(0,_baseUrl.applyConfiguredBaseUrl)(publicClient),(0,_requestDefaults.applyRequestDefaults)(publicClient),(0,_multipart.sendFormDataAsMultipart)(publicClient),(0,_minAppVersion.readMinAppVersion)(publicClient),(0,_retry.attachRetry)(publicClient),publicClient.interceptors.request.use(e=>(_index2.Logger.info(`Request: ${e.method?.toUpperCase()} ${e.url}`),(0,_index.getConfigValue)("logCurl")&&(0,_curl.logCurl)(e),e),e=>{const r=e instanceof Error?e.message:String(e);return _index2.Logger.error("Request error:",r),Promise.reject(e)}),(0,_clientHeaders.applyClientHeaders)(publicClient),publicClient.interceptors.response.use(e=>(_index2.Logger.info(`Response: ${e.status} from ${e.config.url}`),e),e=>{if(_axios.default.isCancel(e))return Promise.reject(e);if(_axios.default.isAxiosError(e)){const r=e.config;if(r?.skipErrorLog)return Promise.reject(e);e.response?_index2.Logger.error(`Response error: ${(0,_responseBodyText.responseBodyText)(e.response.data,e.message)}, URL: ${r?.url}, Status: ${e.response.status}`):_index2.Logger.error(`Network error or no response from server: ${e.message}, URL: ${r?.url}`)}else{const r=e instanceof Error?e.message:String(e);_index2.Logger.error(`Unknown response error: ${r}`)}return Promise.reject(e)});var _default=exports.default=publicClient;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.SESSION_TERMINATION_CODES=exports.SESSION_INVALID_HEADER=exports.RequestOfflineError=void 0,Object.defineProperty(exports,"SIGN_OUT_REASON_HEADER",{enumerable:!0,get:function(){return _tokenRefresh.SIGN_OUT_REASON_HEADER}}),exports.default=void 0,Object.defineProperty(exports,"injectStore",{enumerable:!0,get:function(){return _tokenRefresh.injectStore}}),Object.defineProperty(exports,"injectUserAuth",{enumerable:!0,get:function(){return _tokenRefresh.injectUserAuth}}),Object.defineProperty(exports,"resetRefreshState",{enumerable:!0,get:function(){return _tokenRefresh.resetRefreshState}});var _axios=_interopRequireWildcard(require("axios")),_authStore=require("../auth/authStore.js"),_index=require("../config/index.js"),_index2=require("../logger/index.js"),_networkStatus=require("../network/networkStatus.js");require("../types/axios.js");var _baseUrl=require("./baseUrl.js"),_clientHeaders=require("./clientHeaders.js"),_curl=require("./curl.js"),_minAppVersion=require("./minAppVersion.js"),_multipart=require("./multipart.js"),_requestDefaults=require("./requestDefaults.js"),_responseBodyText=require("./responseBodyText.js"),_retry=require("./retry.js"),_tokenRefresh=require("./tokenRefresh.js");function _interopRequireWildcard(e,r){if("function"==typeof WeakMap)var t=new WeakMap,s=new WeakMap;return(_interopRequireWildcard=function(e,r){if(!r&&e&&e.__esModule)return e;var o,n,i={__proto__:null,default:e};if(null===e||"object"!=typeof e&&"function"!=typeof e)return i;if(o=r?s:t){if(o.has(e))return o.get(e);o.set(e,i)}for(const r in e)"default"!==r&&{}.hasOwnProperty.call(e,r)&&((n=(o=Object.defineProperty)&&Object.getOwnPropertyDescriptor(e,r))&&(n.get||n.set)?o(i,r,n):i[r]=e[r]);return i})(e,r)}class RequestOfflineError extends Error{constructor(e="Request blocked: device is offline"){super(e),this.name="RequestOfflineError",Object.setPrototypeOf(this,RequestOfflineError.prototype)}}exports.RequestOfflineError=RequestOfflineError;const SESSION_TERMINATION_CODES=exports.SESSION_TERMINATION_CODES={TERMINATED:"SESSION_TERMINATED",EXPIRED:"SESSION_EXPIRED"},TERMINATION_CODES=new Set(Object.values(SESSION_TERMINATION_CODES)),SESSION_INVALID_HEADER=exports.SESSION_INVALID_HEADER="x-security-stamp-invalid",hasSessionInvalidHeader=e=>{if(!e||"object"!=typeof e)return!1;const r=e.get,t="function"==typeof r?r.call(e,SESSION_INVALID_HEADER):e[SESSION_INVALID_HEADER];return"true"===String(t).toLowerCase()},getSessionTerminationCode=e=>{if(!e||"object"!=typeof e)return;const r=e,t=r.error,s=[r.code,r.errorCode,t&&"object"==typeof t?t.code:void 0];for(const e of s)if("string"==typeof e&&TERMINATION_CODES.has(e))return e},userClient=_axios.default.create({headers:{"Content-Type":"application/json"}});(0,_minAppVersion.readMinAppVersion)(userClient),(0,_retry.attachRetry)(userClient);const setAuthHeader=(e,r)=>{e.headers instanceof _axios.AxiosHeaders?e.headers.set("Authorization",`Bearer ${r}`):e.headers=_axios.AxiosHeaders.from({...e.headers,Authorization:`Bearer ${r}`})},isRefreshTokenRequest=e=>{const r=(0,_tokenRefresh.getRefreshPath)();return!!r&&!!e?.includes(r)};(0,_baseUrl.applyConfiguredBaseUrl)(userClient),(0,_requestDefaults.applyRequestDefaults)(userClient),(0,_multipart.sendFormDataAsMultipart)(userClient),userClient.interceptors.request.use(e=>((0,_index.getConfigValue)("logCurl")&&(0,_curl.logCurl)(e),e)),(0,_clientHeaders.applyClientHeaders)(userClient),userClient.interceptors.request.use(async e=>{if(!1===(await(0,_networkStatus.getLastKnownNetworkStatus)()).isConnected)return _index2.Logger.warn(`Request blocked: device is offline. URL: ${e.url}`),Promise.reject(new RequestOfflineError);try{if(isRefreshTokenRequest(e.url))return e;const r=await(0,_authStore.getToken)(_authStore.ACCESS_TOKEN_KEY);if(!r?.token)throw new Error("No access token available");const t=Date.now(),s=Date.parse(r.expiry);if(s<=t+3e4){_index2.Logger.info("Access token expired or about to expire, refreshing before request");const r=await(0,_tokenRefresh.refreshAccessToken)();return setAuthHeader(e,r),await(0,_clientHeaders.applyAccountIdHeader)(e.headers,(0,_tokenRefresh.getAccountIdProvider)()),e}return setAuthHeader(e,r.token),await(0,_clientHeaders.applyAccountIdHeader)(e.headers,(0,_tokenRefresh.getAccountIdProvider)()),e}catch(e){return _index2.Logger.error("Failed to attach token:",(0,_tokenRefresh.getErrorMessage)(e)),Promise.reject(e)}},e=>(_index2.Logger.error("Request error:",(0,_tokenRefresh.getErrorMessage)(e)),Promise.reject(e))),userClient.interceptors.response.use(e=>e,async e=>{if(!_axios.default.isAxiosError(e))return _index2.Logger.error(`Non-axios error in response interceptor: ${(0,_tokenRefresh.getErrorMessage)(e)}`),Promise.reject(e);const r=e,t=r.config;if(!r.response)return t?.skipErrorLog||_index2.Logger.error(`Network error or no response from server: ${r.message}, URL: ${t?.url}`),Promise.reject(e);if(401===r.response.status&&t&&!t._retry){if(isRefreshTokenRequest(t.url))return Promise.reject(e);const s=(0,_tokenRefresh.readSignOutReason)(r.response.headers),o=getSessionTerminationCode(r.response.data)??(hasSessionInvalidHeader(r.response.headers)||null!==s?SESSION_TERMINATION_CODES.TERMINATED:void 0);if(o)return _index2.Logger.warn(`Session ${o}, logging out without refresh. URL: ${t.url}`),(0,_tokenRefresh.resetRefreshState)(),await(0,_tokenRefresh.endSessionAndLogout)("terminated",s),Promise.reject(e);if(_index2.Logger.warn(`Unauthorized URL: ${t.url}`),t._retry=!0,(0,_tokenRefresh.isRefreshInFlight)())return(0,_tokenRefresh.queueBehindRefresh)().then(e=>(_index2.Logger.info("Retrying request after token refresh:",t.url),setAuthHeader(t,e),userClient(t))).catch(e=>(_index2.Logger.error("Failed to retry request after token refresh:",(0,_tokenRefresh.getErrorMessage)(e)),Promise.reject(e)));try{const e=await(0,_tokenRefresh.refreshAccessToken)();return setAuthHeader(t,e),userClient(t)}catch(e){return Promise.reject(e)}}return t?.skipErrorLog||_index2.Logger.error(`Response error: ${(0,_responseBodyText.responseBodyText)(r.response?.data,r.message)}, URL: ${t?.url}, Status: ${r.response.status}`),Promise.reject(e)});var _default=exports.default=userClient;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.SESSION_TERMINATION_CODES=exports.SESSION_INVALID_HEADER=exports.RequestOfflineError=void 0,Object.defineProperty(exports,"SIGN_OUT_REASON_HEADER",{enumerable:!0,get:function(){return _tokenRefresh.SIGN_OUT_REASON_HEADER}}),exports.default=void 0,Object.defineProperty(exports,"injectStore",{enumerable:!0,get:function(){return _tokenRefresh.injectStore}}),Object.defineProperty(exports,"injectUserAuth",{enumerable:!0,get:function(){return _tokenRefresh.injectUserAuth}}),Object.defineProperty(exports,"resetRefreshState",{enumerable:!0,get:function(){return _tokenRefresh.resetRefreshState}});var _axios=_interopRequireWildcard(require("axios")),_authStore=require("../auth/authStore.js"),_index=require("../config/index.js"),_index2=require("../logger/index.js"),_networkStatus=require("../network/networkStatus.js");require("../types/axios.js");var _baseUrl=require("./baseUrl.js"),_clientHeaders=require("./clientHeaders.js"),_curl=require("./curl.js"),_minAppVersion=require("./minAppVersion.js"),_multipart=require("./multipart.js"),_requestDefaults=require("./requestDefaults.js"),_responseBodyText=require("./responseBodyText.js"),_retry=require("./retry.js"),_tokenRefresh=require("./tokenRefresh.js");function _interopRequireWildcard(e,r){if("function"==typeof WeakMap)var t=new WeakMap,s=new WeakMap;return(_interopRequireWildcard=function(e,r){if(!r&&e&&e.__esModule)return e;var o,n,i={__proto__:null,default:e};if(null===e||"object"!=typeof e&&"function"!=typeof e)return i;if(o=r?s:t){if(o.has(e))return o.get(e);o.set(e,i)}for(const r in e)"default"!==r&&{}.hasOwnProperty.call(e,r)&&((n=(o=Object.defineProperty)&&Object.getOwnPropertyDescriptor(e,r))&&(n.get||n.set)?o(i,r,n):i[r]=e[r]);return i})(e,r)}class RequestOfflineError extends Error{constructor(e="Request blocked: device is offline"){super(e),this.name="RequestOfflineError",Object.setPrototypeOf(this,RequestOfflineError.prototype)}}exports.RequestOfflineError=RequestOfflineError;class NoAccessTokenError extends Error{constructor(){super("No access token available"),this.name="NoAccessTokenError",Object.setPrototypeOf(this,NoAccessTokenError.prototype)}}const SESSION_TERMINATION_CODES=exports.SESSION_TERMINATION_CODES={TERMINATED:"SESSION_TERMINATED",EXPIRED:"SESSION_EXPIRED"},TERMINATION_CODES=new Set(Object.values(SESSION_TERMINATION_CODES)),SESSION_INVALID_HEADER=exports.SESSION_INVALID_HEADER="x-security-stamp-invalid",hasSessionInvalidHeader=e=>{if(!e||"object"!=typeof e)return!1;const r=e.get,t="function"==typeof r?r.call(e,SESSION_INVALID_HEADER):e[SESSION_INVALID_HEADER];return"true"===String(t).toLowerCase()},getSessionTerminationCode=e=>{if(!e||"object"!=typeof e)return;const r=e,t=r.error,s=[r.code,r.errorCode,t&&"object"==typeof t?t.code:void 0];for(const e of s)if("string"==typeof e&&TERMINATION_CODES.has(e))return e},userClient=_axios.default.create({headers:{"Content-Type":"application/json"}});(0,_minAppVersion.readMinAppVersion)(userClient),(0,_retry.attachRetry)(userClient);const setAuthHeader=(e,r)=>{e.headers instanceof _axios.AxiosHeaders?e.headers.set("Authorization",`Bearer ${r}`):e.headers=_axios.AxiosHeaders.from({...e.headers,Authorization:`Bearer ${r}`})},isRefreshTokenRequest=e=>{const r=(0,_tokenRefresh.getRefreshPath)();return!!r&&!!e?.includes(r)};(0,_baseUrl.applyConfiguredBaseUrl)(userClient),(0,_requestDefaults.applyRequestDefaults)(userClient),(0,_multipart.sendFormDataAsMultipart)(userClient),userClient.interceptors.request.use(e=>((0,_index.getConfigValue)("logCurl")&&(0,_curl.logCurl)(e),e)),(0,_clientHeaders.applyClientHeaders)(userClient),userClient.interceptors.request.use(async e=>{if(!1===(await(0,_networkStatus.getLastKnownNetworkStatus)()).isConnected)return _index2.Logger.warn(`Request blocked: device is offline. URL: ${e.url}`),Promise.reject(new RequestOfflineError);try{if(isRefreshTokenRequest(e.url))return e;const r=await(0,_authStore.getToken)(_authStore.ACCESS_TOKEN_KEY);if(!r?.token)throw new NoAccessTokenError;const t=Date.now(),s=Date.parse(r.expiry);if(s<=t+3e4){_index2.Logger.info("Access token expired or about to expire, refreshing before request");const r=await(0,_tokenRefresh.refreshAccessToken)();return setAuthHeader(e,r),await(0,_clientHeaders.applyAccountIdHeader)(e.headers,(0,_tokenRefresh.getAccountIdProvider)()),e}return setAuthHeader(e,r.token),await(0,_clientHeaders.applyAccountIdHeader)(e.headers,(0,_tokenRefresh.getAccountIdProvider)()),e}catch(r){return r instanceof NoAccessTokenError?_index2.Logger.info(`Request not sent: signed out. URL: ${e.url}`):_index2.Logger.error("Failed to attach token:",(0,_tokenRefresh.getErrorMessage)(r)),Promise.reject(r)}},e=>(_index2.Logger.error("Request error:",(0,_tokenRefresh.getErrorMessage)(e)),Promise.reject(e))),userClient.interceptors.response.use(e=>e,async e=>{if(_axios.default.isCancel(e))return Promise.reject(e);if(e instanceof RequestOfflineError||e instanceof NoAccessTokenError)return Promise.reject(e);if(!_axios.default.isAxiosError(e))return _index2.Logger.error(`Non-axios error in response interceptor: ${(0,_tokenRefresh.getErrorMessage)(e)}`),Promise.reject(e);const r=e,t=r.config;if(!r.response)return t?.skipErrorLog||_index2.Logger.error(`Network error or no response from server: ${r.message}, URL: ${t?.url}`),Promise.reject(e);if(401===r.response.status&&t&&!t._retry){if(isRefreshTokenRequest(t.url))return Promise.reject(e);const s=(0,_tokenRefresh.readSignOutReason)(r.response.headers),o=getSessionTerminationCode(r.response.data)??(hasSessionInvalidHeader(r.response.headers)||null!==s?SESSION_TERMINATION_CODES.TERMINATED:void 0);if(o)return _index2.Logger.warn(`Session ${o}, logging out without refresh. URL: ${t.url}`),(0,_tokenRefresh.resetRefreshState)(),await(0,_tokenRefresh.endSessionAndLogout)("terminated",s),Promise.reject(e);if(_index2.Logger.warn(`Unauthorized URL: ${t.url}`),t._retry=!0,(0,_tokenRefresh.isRefreshInFlight)())return(0,_tokenRefresh.queueBehindRefresh)().then(e=>(_index2.Logger.info("Retrying request after token refresh:",t.url),setAuthHeader(t,e),userClient(t))).catch(e=>(_index2.Logger.error("Failed to retry request after token refresh:",(0,_tokenRefresh.getErrorMessage)(e)),Promise.reject(e)));try{const e=await(0,_tokenRefresh.refreshAccessToken)();return setAuthHeader(t,e),userClient(t)}catch(e){return Promise.reject(e)}}return t?.skipErrorLog||_index2.Logger.error(`Response error: ${(0,_responseBodyText.responseBodyText)(r.response?.data,r.message)}, URL: ${t?.url}, Status: ${r.response.status}`),Promise.reject(e)});var _default=exports.default=userClient;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.setRemoteLogger=exports.setDeviceContext=exports.getDeviceContext=exports.configureLogger=exports.Logger=void 0;var _index=require("../config/index.js"),_redact=require("./redact.js");const Colors={RESET:"",GRAY:"",BLUE:"",YELLOW:"",RED_BOLD:""},LogLevels={DEBUG:{level:"debug",color:Colors.GRAY},INFO:{level:"info",color:Colors.BLUE},WARN:{level:"warn",color:Colors.YELLOW},ERROR:{level:"error",color:Colors.RED_BOLD}},isLogLevelAllowed=e=>{const r=["debug","info","warn","error"],o=(0,_index.getConfigValue)("logLevel","info");return r.indexOf(e)>=r.indexOf(o)},isEnabled=()=>(0,_index.getConfigValue)("loggingEnabled",!0);let remote=null;const setRemoteLogger=e=>{remote=e};exports.setRemoteLogger=setRemoteLogger;const safe=e=>{if(e)try{e()}catch{}},toErrorObject=e=>{if(e instanceof Error)return e;if("string"==typeof e)return new Error(e);try{return new Error(JSON.stringify(e))}catch{return new Error(String(e))}};let deviceContext=null;const normalizeAttributeValue=e=>null==e?null:"string"==typeof e||"number"==typeof e||"boolean"==typeof e?e:String(e),setDeviceContext=e=>{if(deviceContext=e,e)for(const[r,o]of Object.entries(e))safe(()=>remote?.setAttribute?.(r,normalizeAttributeValue(o)))};exports.setDeviceContext=setDeviceContext;const getDeviceContext=()=>deviceContext;exports.getDeviceContext=getDeviceContext;const DEFAULT_LOGGER_OPTIONS={dedupeWindowMs:5e3,dedupeMaxPerWindow:1,screenHistoryLimit:20};let loggerOptions={...DEFAULT_LOGGER_OPTIONS};const configureLogger=e=>{loggerOptions={...loggerOptions,...e},screenHistory.length>loggerOptions.screenHistoryLimit&&(screenHistory=screenHistory.slice(screenHistory.length-loggerOptions.screenHistoryLimit))};exports.configureLogger=configureLogger;let currentScreen=null,screenHistory=[];const dedupeMap=new Map,MAX_DEDUPE_ENTRIES=200,dedupeKeyFor=e=>{const r=e.stack?.split("\n")[1]?.trim();return r?`${e.message}::${r}`:e.message},capDedupeMap=()=>{for(;dedupeMap.size>200;){const e=dedupeMap.keys().next().value;if(void 0===e)return;dedupeMap.delete(e)}},shouldForward=e=>{if(loggerOptions.dedupeWindowMs<=0)return{forward:!0};const r=Date.now(),o=dedupeMap.get(e);if(!o||r-o.windowStart>=loggerOptions.dedupeWindowMs){const t=o?.suppressedCount??0;return dedupeMap.set(e,{windowStart:r,count:1,suppressedCount:0}),capDedupeMap(),t>0?{forward:!0,suppressedCount:t}:{forward:!0}}return o.count+=1,o.count>loggerOptions.dedupeMaxPerWindow?(o.suppressedCount+=1,{forward:!1}):{forward:!0}},buildErrorContext=(e,r)=>{const o={...deviceContext};return null!==currentScreen&&(o.screen=currentScreen),r&&(o.suppressedCount=r),{...o,...e}},forwardErrorToRemote=(e,r)=>{const o=dedupeKeyFor(e),{forward:t,suppressedCount:s}=shouldForward(o);t&&safe(()=>remote?.recordError?.(e,buildErrorContext(r,s)))},logDebug=(e,...r)=>{isEnabled()&&isLogLevelAllowed(LogLevels.DEBUG.level)&&console.debug(`${LogLevels.DEBUG.color}[DEBUG]: ${(0,_redact.redactString)(e)}${Colors.RESET}`,...(0,_redact.redact)(r))},logInfo=(e,...r)=>{const o=(0,_redact.redactString)(e),t=(0,_redact.redact)(r);isEnabled()&&isLogLevelAllowed(LogLevels.INFO.level)&&(console.info(`${LogLevels.INFO.color}[INFO]: ${o}${Colors.RESET}`,...t),safe(()=>remote?.log?.("info",o,t)))},logWarning=(e,...r)=>{const o=(0,_redact.redactString)(e),t=(0,_redact.redact)(r);isEnabled()&&isLogLevelAllowed(LogLevels.WARN.level)&&(console.warn(`${LogLevels.WARN.color}[WARNING]: ${o}${Colors.RESET}`,...t),safe(()=>remote?.log?.("warn",o,t)))},logError=(e,...r)=>{const o=(0,_redact.redactString)(e),t=(0,_redact.redact)(r);isEnabled()&&isLogLevelAllowed(LogLevels.ERROR.level)&&console.error(`${LogLevels.ERROR.color}[ERROR]: ${o}${Colors.RESET}`,...t),safe(()=>remote?.log?.("error",o,t));const s=t[0];void 0!==s&&forwardErrorToRemote(toErrorObject(s),{source:"Logger.error",message:o})},breadcrumb=(e,r)=>{const o=(0,_redact.redactString)(e),t=(0,_redact.redact)(r);isEnabled()&&isLogLevelAllowed("info")&&console.info(`${Colors.BLUE}[BREADCRUMB]: ${o}${Colors.RESET}`,t??""),safe(()=>remote?.breadcrumb?.(o,t))},recordError=(e,r)=>{const o=(0,_redact.redact)(toErrorObject(e)),t=(0,_redact.redact)(r);isEnabled()&&isLogLevelAllowed("error")&&console.error(`${Colors.RED_BOLD}[RECORDED]: ${o.message}${Colors.RESET}`,t??""),forwardErrorToRemote(o,t)},asDict=e=>"object"==typeof e&&null!==e?e:null,headerValue=(e,r)=>{const o=asDict(e);if(!o)return null;const t="function"==typeof o.get?o.get.call(e,r):o[r]??o[r.toLowerCase()];return"string"==typeof t&&""!==t?t:null},requestFailed=(e,r={})=>{const{label:o,expectedStatuses:t=[],...s}=r,n=asDict(e),l=asDict(n?.config),c=asDict(n?.response),i=asDict(c?.data),d="number"==typeof c?.status?c.status:null,a="string"==typeof l?.method?l.method.toUpperCase():null,g="string"==typeof l?.url?l.url.split("?")[0]??null:null,u=o??([a,g].filter(Boolean).join(" ")||"Request");if("CanceledError"===n?.name||"ERR_CANCELED"===n?.code||!0===n?.__CANCEL__)return void logInfo(`${u} canceled`);if(null!==d&&t.includes(d))return void logWarning(`${u} refused (${d})`);const p="string"==typeof n?.message?n.message:e instanceof Error?e.message:null,E=(0,_redact.redactString)(null===d?`${u} failed`:`${u} failed (${d})`);isEnabled()&&isLogLevelAllowed(LogLevels.ERROR.level)&&console.error(`${LogLevels.ERROR.color}[ERROR]: ${E}${Colors.RESET}`,(0,_redact.redactString)(p??"")),safe(()=>remote?.log?.("error",E,[]));const f=(0,_index.getConfigValue)("correlationIdHeader",null),L=(0,_redact.redact)({source:"Logger.requestFailed",label:u,status:d,method:a,path:g,code:"string"==typeof i?.code?i.code:null,traceId:"string"==typeof i?.traceId?i.traceId:null,correlationId:f?headerValue(l?.headers,f):null,detail:p,...s});forwardErrorToRemote((0,_redact.redact)(new Error(E)),L)},setUser=(e,r)=>{safe(()=>remote?.setUser?.(e,r))},setAttribute=(e,r)=>{safe(()=>remote?.setAttribute?.(e,r))},trackScreen=(e,r)=>{e!==currentScreen&&(currentScreen=e,screenHistory.push(e),screenHistory.length>loggerOptions.screenHistoryLimit&&(screenHistory=screenHistory.slice(screenHistory.length-loggerOptions.screenHistoryLimit)),breadcrumb(`Screen: ${e}`,r))},getCurrentScreen=()=>currentScreen,getScreenHistory=()=>[...screenHistory],Logger=exports.Logger={debug:logDebug,info:logInfo,warn:logWarning,error:logError,breadcrumb:breadcrumb,recordError:recordError,requestFailed:requestFailed,setUser:setUser,setAttribute:setAttribute,trackScreen:trackScreen,getCurrentScreen:getCurrentScreen,getScreenHistory:getScreenHistory};
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.setRemoteLogger=exports.setDeviceContext=exports.getDeviceContext=exports.configureLogger=exports.Logger=void 0;var _index=require("../config/index.js"),_redact=require("./redact.js");const Colors={RESET:"",GRAY:"",BLUE:"",YELLOW:"",RED_BOLD:""},LogLevels={DEBUG:{level:"debug",color:Colors.GRAY},INFO:{level:"info",color:Colors.BLUE},WARN:{level:"warn",color:Colors.YELLOW},ERROR:{level:"error",color:Colors.RED_BOLD}},isLogLevelAllowed=e=>{const r=["debug","info","warn","error"],o=(0,_index.getConfigValue)("logLevel","info");return r.indexOf(e)>=r.indexOf(o)},isEnabled=()=>(0,_index.getConfigValue)("loggingEnabled",!0);let remote=null;const setRemoteLogger=e=>{remote=e};exports.setRemoteLogger=setRemoteLogger;const safe=e=>{if(e)try{e()}catch{}},toErrorObject=e=>{if(e instanceof Error)return e;if("string"==typeof e)return new Error(e);try{return new Error(JSON.stringify(e))}catch{return new Error(String(e))}};let deviceContext=null;const normalizeAttributeValue=e=>null==e?null:"string"==typeof e||"number"==typeof e||"boolean"==typeof e?e:String(e),setDeviceContext=e=>{if(deviceContext=e,e)for(const[r,o]of Object.entries(e))safe(()=>remote?.setAttribute?.(r,normalizeAttributeValue(o)))};exports.setDeviceContext=setDeviceContext;const getDeviceContext=()=>deviceContext;exports.getDeviceContext=getDeviceContext;const DEFAULT_LOGGER_OPTIONS={dedupeWindowMs:5e3,dedupeMaxPerWindow:1,screenHistoryLimit:20};let loggerOptions={...DEFAULT_LOGGER_OPTIONS};const configureLogger=e=>{loggerOptions={...loggerOptions,...e},screenHistory.length>loggerOptions.screenHistoryLimit&&(screenHistory=screenHistory.slice(screenHistory.length-loggerOptions.screenHistoryLimit))};exports.configureLogger=configureLogger;let currentScreen=null,screenHistory=[];const dedupeMap=new Map,MAX_DEDUPE_ENTRIES=200,dedupeKeyFor=e=>{const r=e.stack?.split("\n")[1]?.trim();return r?`${e.message}::${r}`:e.message},capDedupeMap=()=>{for(;dedupeMap.size>200;){const e=dedupeMap.keys().next().value;if(void 0===e)return;dedupeMap.delete(e)}},shouldForward=e=>{if(loggerOptions.dedupeWindowMs<=0)return{forward:!0};const r=Date.now(),o=dedupeMap.get(e);if(!o||r-o.windowStart>=loggerOptions.dedupeWindowMs){const t=o?.suppressedCount??0;return dedupeMap.set(e,{windowStart:r,count:1,suppressedCount:0}),capDedupeMap(),t>0?{forward:!0,suppressedCount:t}:{forward:!0}}return o.count+=1,o.count>loggerOptions.dedupeMaxPerWindow?(o.suppressedCount+=1,{forward:!1}):{forward:!0}},buildErrorContext=(e,r)=>{const o={...deviceContext};return null!==currentScreen&&(o.screen=currentScreen),r&&(o.suppressedCount=r),{...o,...e}},forwardErrorToRemote=(e,r)=>{const o=dedupeKeyFor(e),{forward:t,suppressedCount:s}=shouldForward(o);t&&safe(()=>remote?.recordError?.(e,buildErrorContext(r,s)))},logDebug=(e,...r)=>{isEnabled()&&isLogLevelAllowed(LogLevels.DEBUG.level)&&console.debug(`${LogLevels.DEBUG.color}[DEBUG]: ${(0,_redact.redactString)(e)}${Colors.RESET}`,...(0,_redact.redact)(r))},logInfo=(e,...r)=>{const o=(0,_redact.redactString)(e),t=(0,_redact.redact)(r);isEnabled()&&isLogLevelAllowed(LogLevels.INFO.level)&&(console.info(`${LogLevels.INFO.color}[INFO]: ${o}${Colors.RESET}`,...t),safe(()=>remote?.log?.("info",o,t)))},logWarning=(e,...r)=>{const o=(0,_redact.redactString)(e),t=(0,_redact.redact)(r);isEnabled()&&isLogLevelAllowed(LogLevels.WARN.level)&&(console.warn(`${LogLevels.WARN.color}[WARNING]: ${o}${Colors.RESET}`,...t),safe(()=>remote?.log?.("warn",o,t)))},logError=(e,...r)=>{const o=(0,_redact.redactString)(e),t=(0,_redact.redact)(r);isEnabled()&&isLogLevelAllowed(LogLevels.ERROR.level)&&console.error(`${LogLevels.ERROR.color}[ERROR]: ${o}${Colors.RESET}`,...t),safe(()=>remote?.log?.("error",o,t));const s=t[0];void 0!==s&&forwardErrorToRemote(toErrorObject(s),{source:"Logger.error",message:o})},breadcrumb=(e,r)=>{const o=(0,_redact.redactString)(e),t=(0,_redact.redact)(r);isEnabled()&&isLogLevelAllowed("info")&&console.info(`${Colors.BLUE}[BREADCRUMB]: ${o}${Colors.RESET}`,t??""),safe(()=>remote?.breadcrumb?.(o,t))},recordError=(e,r)=>{const o=(0,_redact.redact)(toErrorObject(e)),t=(0,_redact.redact)(r);isEnabled()&&isLogLevelAllowed("error")&&console.error(`${Colors.RED_BOLD}[RECORDED]: ${o.message}${Colors.RESET}`,t??""),forwardErrorToRemote(o,t)},asDict=e=>"object"==typeof e&&null!==e?e:null,headerValue=(e,r)=>{const o=asDict(e);if(!o)return null;const t="function"==typeof o.get?o.get.call(e,r):o[r]??o[r.toLowerCase()];return"string"==typeof t&&""!==t?t:null},requestFailed=(e,r={})=>{const{label:o,expectedStatuses:t=[],...s}=r,n=asDict(e),l=asDict(n?.config),c=asDict(n?.response),i=asDict(c?.data),d="number"==typeof c?.status?c.status:null,a="string"==typeof l?.method?l.method.toUpperCase():null,g="string"==typeof l?.url?l.url.split("?")[0]??null:null,u=o??([a,g].filter(Boolean).join(" ")||"Request");if("CanceledError"===n?.name||"ERR_CANCELED"===n?.code||!0===n?.__CANCEL__)return void logInfo(`${u} canceled`);if(null!==d&&t.includes(d))return void logInfo(`${u} refused (${d})`);const p="string"==typeof n?.message?n.message:e instanceof Error?e.message:null,E=(0,_redact.redactString)(null===d?`${u} failed`:`${u} failed (${d})`);isEnabled()&&isLogLevelAllowed(LogLevels.ERROR.level)&&console.error(`${LogLevels.ERROR.color}[ERROR]: ${E}${Colors.RESET}`,(0,_redact.redactString)(p??"")),safe(()=>remote?.log?.("error",E,[]));const f=(0,_index.getConfigValue)("correlationIdHeader",null),L=(0,_redact.redact)({source:"Logger.requestFailed",label:u,status:d,method:a,path:g,code:"string"==typeof i?.code?i.code:null,traceId:"string"==typeof i?.traceId?i.traceId:null,correlationId:f?headerValue(l?.headers,f):null,detail:p,...s});forwardErrorToRemote((0,_redact.redact)(new Error(E)),L)},setUser=(e,r)=>{safe(()=>remote?.setUser?.(e,r))},setAttribute=(e,r)=>{safe(()=>remote?.setAttribute?.(e,r))},trackScreen=(e,r)=>{e!==currentScreen&&(currentScreen=e,screenHistory.push(e),screenHistory.length>loggerOptions.screenHistoryLimit&&(screenHistory=screenHistory.slice(screenHistory.length-loggerOptions.screenHistoryLimit)),breadcrumb(`Screen: ${e}`,r))},getCurrentScreen=()=>currentScreen,getScreenHistory=()=>[...screenHistory],Logger=exports.Logger={debug:logDebug,info:logInfo,warn:logWarning,error:logError,breadcrumb:breadcrumb,recordError:recordError,requestFailed:requestFailed,setUser:setUser,setAttribute:setAttribute,trackScreen:trackScreen,getCurrentScreen:getCurrentScreen,getScreenHistory:getScreenHistory};
@@ -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,"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,"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,"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,"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.toPushAction=exports.toNotificationActions=exports.setPushImplementation=exports.setCategories=exports.setBadgeCount=exports.routeTaskPayload=exports.resolveModules=exports.onAction=exports.notifyLocal=exports.initPush=exports.enablePush=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}}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,listening=!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?"denied":"unknown",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.notification.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.notification.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)},responseData=e=>({...triggerData(e),...contentData(e)}),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)}},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)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}},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;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()=>{requireConfig();const e=resolveModules();if(!e)return setState({status:"unavailable"}),"unavailable";const t=e.notifications;await channelsReady;let n=await t.getPermissionsAsync();if(n.granted||(n=await t.requestPermissionsAsync()),!n.granted)return setState({status:"denied"}),"denied";const o=await toDevice(await t.getDevicePushTokenAsync(),e.applicationId);return o?(await registerOnce(o),tokenSubscription?.remove(),tokenSubscription=t.addPushTokenListener(t=>{toDevice(t,e.applicationId).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 disablePush=async()=>{const e=requireConfig();tokenSubscription?.remove(),tokenSubscription=null,registeredKey=null,setState({enabled:!1}),await e.unregister(await e.getInstallationId())};exports.disablePush=disablePush;const setBadgeCount=async e=>{const t=resolveModules();t&&await t.notifications.setBadgeCountAsync(e)};exports.setBadgeCount=setBadgeCount;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 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.onReceive=exports.onAction=exports.notifyLocal=exports.initPush=exports.enablePush=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?"denied":"unknown",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}},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}},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;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;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=>{toDevice(e,t).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 disablePush=async()=>{const e=requireConfig();tokenSubscription?.remove(),tokenSubscription=null,registeredKey=null,setState({enabled:!1}),await e.unregister(await e.getInstallationId())};exports.disablePush=disablePush;const setBadgeCount=async e=>{const t=resolveModules();t&&await t.notifications.setBadgeCountAsync(e)};exports.setBadgeCount=setBadgeCount;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";import axios from"axios";import{getConfigValue}from"../config/index.js";import{Logger}from"../logger/index.js";import"../types/axios.js";import{applyConfiguredBaseUrl}from"./baseUrl.js";import{applyClientHeaders}from"./clientHeaders.js";import{logCurl}from"./curl.js";import{readMinAppVersion}from"./minAppVersion.js";import{sendFormDataAsMultipart}from"./multipart.js";import{applyRequestDefaults}from"./requestDefaults.js";import{responseBodyText}from"./responseBodyText.js";import{attachRetry}from"./retry.js";const publicClient=axios.create({headers:{"Content-Type":"application/json",Accept:"application/json"}});applyConfiguredBaseUrl(publicClient),applyRequestDefaults(publicClient),sendFormDataAsMultipart(publicClient),readMinAppVersion(publicClient),attachRetry(publicClient),publicClient.interceptors.request.use(e=>(Logger.info(`Request: ${e.method?.toUpperCase()} ${e.url}`),getConfigValue("logCurl")&&logCurl(e),e),e=>{const r=e instanceof Error?e.message:String(e);return Logger.error("Request error:",r),Promise.reject(e)}),applyClientHeaders(publicClient),publicClient.interceptors.response.use(e=>(Logger.info(`Response: ${e.status} from ${e.config.url}`),e),e=>{if(axios.isAxiosError(e)){const r=e.config;if(r?.skipErrorLog)return Promise.reject(e);e.response?Logger.error(`Response error: ${responseBodyText(e.response.data,e.message)}, URL: ${r?.url}, Status: ${e.response.status}`):Logger.error(`Network error or no response from server: ${e.message}, URL: ${r?.url}`)}else{const r=e instanceof Error?e.message:String(e);Logger.error(`Unknown response error: ${r}`)}return Promise.reject(e)});export default publicClient;
1
+ "use strict";import axios from"axios";import{getConfigValue}from"../config/index.js";import{Logger}from"../logger/index.js";import"../types/axios.js";import{applyConfiguredBaseUrl}from"./baseUrl.js";import{applyClientHeaders}from"./clientHeaders.js";import{logCurl}from"./curl.js";import{readMinAppVersion}from"./minAppVersion.js";import{sendFormDataAsMultipart}from"./multipart.js";import{applyRequestDefaults}from"./requestDefaults.js";import{responseBodyText}from"./responseBodyText.js";import{attachRetry}from"./retry.js";const publicClient=axios.create({headers:{"Content-Type":"application/json",Accept:"application/json"}});applyConfiguredBaseUrl(publicClient),applyRequestDefaults(publicClient),sendFormDataAsMultipart(publicClient),readMinAppVersion(publicClient),attachRetry(publicClient),publicClient.interceptors.request.use(e=>(Logger.info(`Request: ${e.method?.toUpperCase()} ${e.url}`),getConfigValue("logCurl")&&logCurl(e),e),e=>{const r=e instanceof Error?e.message:String(e);return Logger.error("Request error:",r),Promise.reject(e)}),applyClientHeaders(publicClient),publicClient.interceptors.response.use(e=>(Logger.info(`Response: ${e.status} from ${e.config.url}`),e),e=>{if(axios.isCancel(e))return Promise.reject(e);if(axios.isAxiosError(e)){const r=e.config;if(r?.skipErrorLog)return Promise.reject(e);e.response?Logger.error(`Response error: ${responseBodyText(e.response.data,e.message)}, URL: ${r?.url}, Status: ${e.response.status}`):Logger.error(`Network error or no response from server: ${e.message}, URL: ${r?.url}`)}else{const r=e instanceof Error?e.message:String(e);Logger.error(`Unknown response error: ${r}`)}return Promise.reject(e)});export default publicClient;
@@ -1 +1 @@
1
- "use strict";import axios,{AxiosHeaders}from"axios";import{ACCESS_TOKEN_KEY,getToken}from"../auth/authStore.js";import{getConfigValue}from"../config/index.js";import{Logger}from"../logger/index.js";import{getLastKnownNetworkStatus}from"../network/networkStatus.js";import"../types/axios.js";import{applyConfiguredBaseUrl}from"./baseUrl.js";import{applyAccountIdHeader,applyClientHeaders}from"./clientHeaders.js";import{logCurl}from"./curl.js";import{readMinAppVersion}from"./minAppVersion.js";import{sendFormDataAsMultipart}from"./multipart.js";import{applyRequestDefaults}from"./requestDefaults.js";import{responseBodyText}from"./responseBodyText.js";import{attachRetry}from"./retry.js";import{endSessionAndLogout,getAccountIdProvider,getErrorMessage,getRefreshPath,isRefreshInFlight,queueBehindRefresh,readSignOutReason,refreshAccessToken,resetRefreshState}from"./tokenRefresh.js";export{injectStore,injectUserAuth,resetRefreshState,SIGN_OUT_REASON_HEADER}from"./tokenRefresh.js";export class RequestOfflineError extends Error{constructor(e="Request blocked: device is offline"){super(e),this.name="RequestOfflineError",Object.setPrototypeOf(this,RequestOfflineError.prototype)}}export const SESSION_TERMINATION_CODES={TERMINATED:"SESSION_TERMINATED",EXPIRED:"SESSION_EXPIRED"};const TERMINATION_CODES=new Set(Object.values(SESSION_TERMINATION_CODES));export const SESSION_INVALID_HEADER="x-security-stamp-invalid";const hasSessionInvalidHeader=e=>{if(!e||"object"!=typeof e)return!1;const r=e.get,t="function"==typeof r?r.call(e,SESSION_INVALID_HEADER):e[SESSION_INVALID_HEADER];return"true"===String(t).toLowerCase()},getSessionTerminationCode=e=>{if(!e||"object"!=typeof e)return;const r=e,t=r.error,s=[r.code,r.errorCode,t&&"object"==typeof t?t.code:void 0];for(const e of s)if("string"==typeof e&&TERMINATION_CODES.has(e))return e},userClient=axios.create({headers:{"Content-Type":"application/json"}});readMinAppVersion(userClient),attachRetry(userClient);const setAuthHeader=(e,r)=>{e.headers instanceof AxiosHeaders?e.headers.set("Authorization",`Bearer ${r}`):e.headers=AxiosHeaders.from({...e.headers,Authorization:`Bearer ${r}`})},isRefreshTokenRequest=e=>{const r=getRefreshPath();return!!r&&!!e?.includes(r)};applyConfiguredBaseUrl(userClient),applyRequestDefaults(userClient),sendFormDataAsMultipart(userClient),userClient.interceptors.request.use(e=>(getConfigValue("logCurl")&&logCurl(e),e)),applyClientHeaders(userClient),userClient.interceptors.request.use(async e=>{if(!1===(await getLastKnownNetworkStatus()).isConnected)return Logger.warn(`Request blocked: device is offline. URL: ${e.url}`),Promise.reject(new RequestOfflineError);try{if(isRefreshTokenRequest(e.url))return e;const r=await getToken(ACCESS_TOKEN_KEY);if(!r?.token)throw new Error("No access token available");const t=Date.now(),s=Date.parse(r.expiry);if(s<=t+3e4){Logger.info("Access token expired or about to expire, refreshing before request");const r=await refreshAccessToken();return setAuthHeader(e,r),await applyAccountIdHeader(e.headers,getAccountIdProvider()),e}return setAuthHeader(e,r.token),await applyAccountIdHeader(e.headers,getAccountIdProvider()),e}catch(e){return Logger.error("Failed to attach token:",getErrorMessage(e)),Promise.reject(e)}},e=>(Logger.error("Request error:",getErrorMessage(e)),Promise.reject(e))),userClient.interceptors.response.use(e=>e,async e=>{if(!axios.isAxiosError(e))return Logger.error(`Non-axios error in response interceptor: ${getErrorMessage(e)}`),Promise.reject(e);const r=e,t=r.config;if(!r.response)return t?.skipErrorLog||Logger.error(`Network error or no response from server: ${r.message}, URL: ${t?.url}`),Promise.reject(e);if(401===r.response.status&&t&&!t._retry){if(isRefreshTokenRequest(t.url))return Promise.reject(e);const s=readSignOutReason(r.response.headers),o=getSessionTerminationCode(r.response.data)??(hasSessionInvalidHeader(r.response.headers)||null!==s?SESSION_TERMINATION_CODES.TERMINATED:void 0);if(o)return Logger.warn(`Session ${o}, logging out without refresh. URL: ${t.url}`),resetRefreshState(),await endSessionAndLogout("terminated",s),Promise.reject(e);if(Logger.warn(`Unauthorized URL: ${t.url}`),t._retry=!0,isRefreshInFlight())return queueBehindRefresh().then(e=>(Logger.info("Retrying request after token refresh:",t.url),setAuthHeader(t,e),userClient(t))).catch(e=>(Logger.error("Failed to retry request after token refresh:",getErrorMessage(e)),Promise.reject(e)));try{const e=await refreshAccessToken();return setAuthHeader(t,e),userClient(t)}catch(e){return Promise.reject(e)}}return t?.skipErrorLog||Logger.error(`Response error: ${responseBodyText(r.response?.data,r.message)}, URL: ${t?.url}, Status: ${r.response.status}`),Promise.reject(e)});export default userClient;
1
+ "use strict";import axios,{AxiosHeaders}from"axios";import{ACCESS_TOKEN_KEY,getToken}from"../auth/authStore.js";import{getConfigValue}from"../config/index.js";import{Logger}from"../logger/index.js";import{getLastKnownNetworkStatus}from"../network/networkStatus.js";import"../types/axios.js";import{applyConfiguredBaseUrl}from"./baseUrl.js";import{applyAccountIdHeader,applyClientHeaders}from"./clientHeaders.js";import{logCurl}from"./curl.js";import{readMinAppVersion}from"./minAppVersion.js";import{sendFormDataAsMultipart}from"./multipart.js";import{applyRequestDefaults}from"./requestDefaults.js";import{responseBodyText}from"./responseBodyText.js";import{attachRetry}from"./retry.js";import{endSessionAndLogout,getAccountIdProvider,getErrorMessage,getRefreshPath,isRefreshInFlight,queueBehindRefresh,readSignOutReason,refreshAccessToken,resetRefreshState}from"./tokenRefresh.js";export{injectStore,injectUserAuth,resetRefreshState,SIGN_OUT_REASON_HEADER}from"./tokenRefresh.js";export class RequestOfflineError extends Error{constructor(e="Request blocked: device is offline"){super(e),this.name="RequestOfflineError",Object.setPrototypeOf(this,RequestOfflineError.prototype)}}class NoAccessTokenError extends Error{constructor(){super("No access token available"),this.name="NoAccessTokenError",Object.setPrototypeOf(this,NoAccessTokenError.prototype)}}export const SESSION_TERMINATION_CODES={TERMINATED:"SESSION_TERMINATED",EXPIRED:"SESSION_EXPIRED"};const TERMINATION_CODES=new Set(Object.values(SESSION_TERMINATION_CODES));export const SESSION_INVALID_HEADER="x-security-stamp-invalid";const hasSessionInvalidHeader=e=>{if(!e||"object"!=typeof e)return!1;const r=e.get,t="function"==typeof r?r.call(e,SESSION_INVALID_HEADER):e[SESSION_INVALID_HEADER];return"true"===String(t).toLowerCase()},getSessionTerminationCode=e=>{if(!e||"object"!=typeof e)return;const r=e,t=r.error,s=[r.code,r.errorCode,t&&"object"==typeof t?t.code:void 0];for(const e of s)if("string"==typeof e&&TERMINATION_CODES.has(e))return e},userClient=axios.create({headers:{"Content-Type":"application/json"}});readMinAppVersion(userClient),attachRetry(userClient);const setAuthHeader=(e,r)=>{e.headers instanceof AxiosHeaders?e.headers.set("Authorization",`Bearer ${r}`):e.headers=AxiosHeaders.from({...e.headers,Authorization:`Bearer ${r}`})},isRefreshTokenRequest=e=>{const r=getRefreshPath();return!!r&&!!e?.includes(r)};applyConfiguredBaseUrl(userClient),applyRequestDefaults(userClient),sendFormDataAsMultipart(userClient),userClient.interceptors.request.use(e=>(getConfigValue("logCurl")&&logCurl(e),e)),applyClientHeaders(userClient),userClient.interceptors.request.use(async e=>{if(!1===(await getLastKnownNetworkStatus()).isConnected)return Logger.warn(`Request blocked: device is offline. URL: ${e.url}`),Promise.reject(new RequestOfflineError);try{if(isRefreshTokenRequest(e.url))return e;const r=await getToken(ACCESS_TOKEN_KEY);if(!r?.token)throw new NoAccessTokenError;const t=Date.now(),s=Date.parse(r.expiry);if(s<=t+3e4){Logger.info("Access token expired or about to expire, refreshing before request");const r=await refreshAccessToken();return setAuthHeader(e,r),await applyAccountIdHeader(e.headers,getAccountIdProvider()),e}return setAuthHeader(e,r.token),await applyAccountIdHeader(e.headers,getAccountIdProvider()),e}catch(r){return r instanceof NoAccessTokenError?Logger.info(`Request not sent: signed out. URL: ${e.url}`):Logger.error("Failed to attach token:",getErrorMessage(r)),Promise.reject(r)}},e=>(Logger.error("Request error:",getErrorMessage(e)),Promise.reject(e))),userClient.interceptors.response.use(e=>e,async e=>{if(axios.isCancel(e))return Promise.reject(e);if(e instanceof RequestOfflineError||e instanceof NoAccessTokenError)return Promise.reject(e);if(!axios.isAxiosError(e))return Logger.error(`Non-axios error in response interceptor: ${getErrorMessage(e)}`),Promise.reject(e);const r=e,t=r.config;if(!r.response)return t?.skipErrorLog||Logger.error(`Network error or no response from server: ${r.message}, URL: ${t?.url}`),Promise.reject(e);if(401===r.response.status&&t&&!t._retry){if(isRefreshTokenRequest(t.url))return Promise.reject(e);const s=readSignOutReason(r.response.headers),o=getSessionTerminationCode(r.response.data)??(hasSessionInvalidHeader(r.response.headers)||null!==s?SESSION_TERMINATION_CODES.TERMINATED:void 0);if(o)return Logger.warn(`Session ${o}, logging out without refresh. URL: ${t.url}`),resetRefreshState(),await endSessionAndLogout("terminated",s),Promise.reject(e);if(Logger.warn(`Unauthorized URL: ${t.url}`),t._retry=!0,isRefreshInFlight())return queueBehindRefresh().then(e=>(Logger.info("Retrying request after token refresh:",t.url),setAuthHeader(t,e),userClient(t))).catch(e=>(Logger.error("Failed to retry request after token refresh:",getErrorMessage(e)),Promise.reject(e)));try{const e=await refreshAccessToken();return setAuthHeader(t,e),userClient(t)}catch(e){return Promise.reject(e)}}return t?.skipErrorLog||Logger.error(`Response error: ${responseBodyText(r.response?.data,r.message)}, URL: ${t?.url}, Status: ${r.response.status}`),Promise.reject(e)});export default userClient;
@@ -1 +1 @@
1
- "use strict";import{getConfigValue}from"../config/index.js";import{redact,redactString}from"./redact.js";const Colors={RESET:"",GRAY:"",BLUE:"",YELLOW:"",RED_BOLD:""},LogLevels={DEBUG:{level:"debug",color:Colors.GRAY},INFO:{level:"info",color:Colors.BLUE},WARN:{level:"warn",color:Colors.YELLOW},ERROR:{level:"error",color:Colors.RED_BOLD}},isLogLevelAllowed=e=>{const r=["debug","info","warn","error"],o=getConfigValue("logLevel","info");return r.indexOf(e)>=r.indexOf(o)},isEnabled=()=>getConfigValue("loggingEnabled",!0);let remote=null;export const setRemoteLogger=e=>{remote=e};const safe=e=>{if(e)try{e()}catch{}},toErrorObject=e=>{if(e instanceof Error)return e;if("string"==typeof e)return new Error(e);try{return new Error(JSON.stringify(e))}catch{return new Error(String(e))}};let deviceContext=null;const normalizeAttributeValue=e=>null==e?null:"string"==typeof e||"number"==typeof e||"boolean"==typeof e?e:String(e);export const setDeviceContext=e=>{if(deviceContext=e,e)for(const[r,o]of Object.entries(e))safe(()=>remote?.setAttribute?.(r,normalizeAttributeValue(o)))};export const getDeviceContext=()=>deviceContext;const DEFAULT_LOGGER_OPTIONS={dedupeWindowMs:5e3,dedupeMaxPerWindow:1,screenHistoryLimit:20};let loggerOptions={...DEFAULT_LOGGER_OPTIONS};export const configureLogger=e=>{loggerOptions={...loggerOptions,...e},screenHistory.length>loggerOptions.screenHistoryLimit&&(screenHistory=screenHistory.slice(screenHistory.length-loggerOptions.screenHistoryLimit))};let currentScreen=null,screenHistory=[];const dedupeMap=new Map,MAX_DEDUPE_ENTRIES=200,dedupeKeyFor=e=>{const r=e.stack?.split("\n")[1]?.trim();return r?`${e.message}::${r}`:e.message},capDedupeMap=()=>{for(;dedupeMap.size>200;){const e=dedupeMap.keys().next().value;if(void 0===e)return;dedupeMap.delete(e)}},shouldForward=e=>{if(loggerOptions.dedupeWindowMs<=0)return{forward:!0};const r=Date.now(),o=dedupeMap.get(e);if(!o||r-o.windowStart>=loggerOptions.dedupeWindowMs){const t=o?.suppressedCount??0;return dedupeMap.set(e,{windowStart:r,count:1,suppressedCount:0}),capDedupeMap(),t>0?{forward:!0,suppressedCount:t}:{forward:!0}}return o.count+=1,o.count>loggerOptions.dedupeMaxPerWindow?(o.suppressedCount+=1,{forward:!1}):{forward:!0}},buildErrorContext=(e,r)=>{const o={...deviceContext};return null!==currentScreen&&(o.screen=currentScreen),r&&(o.suppressedCount=r),{...o,...e}},forwardErrorToRemote=(e,r)=>{const o=dedupeKeyFor(e),{forward:t,suppressedCount:n}=shouldForward(o);t&&safe(()=>remote?.recordError?.(e,buildErrorContext(r,n)))},logDebug=(e,...r)=>{isEnabled()&&isLogLevelAllowed(LogLevels.DEBUG.level)&&console.debug(`${LogLevels.DEBUG.color}[DEBUG]: ${redactString(e)}${Colors.RESET}`,...redact(r))},logInfo=(e,...r)=>{const o=redactString(e),t=redact(r);isEnabled()&&isLogLevelAllowed(LogLevels.INFO.level)&&(console.info(`${LogLevels.INFO.color}[INFO]: ${o}${Colors.RESET}`,...t),safe(()=>remote?.log?.("info",o,t)))},logWarning=(e,...r)=>{const o=redactString(e),t=redact(r);isEnabled()&&isLogLevelAllowed(LogLevels.WARN.level)&&(console.warn(`${LogLevels.WARN.color}[WARNING]: ${o}${Colors.RESET}`,...t),safe(()=>remote?.log?.("warn",o,t)))},logError=(e,...r)=>{const o=redactString(e),t=redact(r);isEnabled()&&isLogLevelAllowed(LogLevels.ERROR.level)&&console.error(`${LogLevels.ERROR.color}[ERROR]: ${o}${Colors.RESET}`,...t),safe(()=>remote?.log?.("error",o,t));const n=t[0];void 0!==n&&forwardErrorToRemote(toErrorObject(n),{source:"Logger.error",message:o})},breadcrumb=(e,r)=>{const o=redactString(e),t=redact(r);isEnabled()&&isLogLevelAllowed("info")&&console.info(`${Colors.BLUE}[BREADCRUMB]: ${o}${Colors.RESET}`,t??""),safe(()=>remote?.breadcrumb?.(o,t))},recordError=(e,r)=>{const o=redact(toErrorObject(e)),t=redact(r);isEnabled()&&isLogLevelAllowed("error")&&console.error(`${Colors.RED_BOLD}[RECORDED]: ${o.message}${Colors.RESET}`,t??""),forwardErrorToRemote(o,t)},asDict=e=>"object"==typeof e&&null!==e?e:null,headerValue=(e,r)=>{const o=asDict(e);if(!o)return null;const t="function"==typeof o.get?o.get.call(e,r):o[r]??o[r.toLowerCase()];return"string"==typeof t&&""!==t?t:null},requestFailed=(e,r={})=>{const{label:o,expectedStatuses:t=[],...n}=r,s=asDict(e),l=asDict(s?.config),c=asDict(s?.response),i=asDict(c?.data),d="number"==typeof c?.status?c.status:null,a="string"==typeof l?.method?l.method.toUpperCase():null,g="string"==typeof l?.url?l.url.split("?")[0]??null:null,u=o??([a,g].filter(Boolean).join(" ")||"Request");if("CanceledError"===s?.name||"ERR_CANCELED"===s?.code||!0===s?.__CANCEL__)return void logInfo(`${u} canceled`);if(null!==d&&t.includes(d))return void logWarning(`${u} refused (${d})`);const E="string"==typeof s?.message?s.message:e instanceof Error?e.message:null,f=redactString(null===d?`${u} failed`:`${u} failed (${d})`);isEnabled()&&isLogLevelAllowed(LogLevels.ERROR.level)&&console.error(`${LogLevels.ERROR.color}[ERROR]: ${f}${Colors.RESET}`,redactString(E??"")),safe(()=>remote?.log?.("error",f,[]));const p=getConfigValue("correlationIdHeader",null),L=redact({source:"Logger.requestFailed",label:u,status:d,method:a,path:g,code:"string"==typeof i?.code?i.code:null,traceId:"string"==typeof i?.traceId?i.traceId:null,correlationId:p?headerValue(l?.headers,p):null,detail:E,...n});forwardErrorToRemote(redact(new Error(f)),L)},setUser=(e,r)=>{safe(()=>remote?.setUser?.(e,r))},setAttribute=(e,r)=>{safe(()=>remote?.setAttribute?.(e,r))},trackScreen=(e,r)=>{e!==currentScreen&&(currentScreen=e,screenHistory.push(e),screenHistory.length>loggerOptions.screenHistoryLimit&&(screenHistory=screenHistory.slice(screenHistory.length-loggerOptions.screenHistoryLimit)),breadcrumb(`Screen: ${e}`,r))},getCurrentScreen=()=>currentScreen,getScreenHistory=()=>[...screenHistory];export const Logger={debug:logDebug,info:logInfo,warn:logWarning,error:logError,breadcrumb:breadcrumb,recordError:recordError,requestFailed:requestFailed,setUser:setUser,setAttribute:setAttribute,trackScreen:trackScreen,getCurrentScreen:getCurrentScreen,getScreenHistory:getScreenHistory};
1
+ "use strict";import{getConfigValue}from"../config/index.js";import{redact,redactString}from"./redact.js";const Colors={RESET:"",GRAY:"",BLUE:"",YELLOW:"",RED_BOLD:""},LogLevels={DEBUG:{level:"debug",color:Colors.GRAY},INFO:{level:"info",color:Colors.BLUE},WARN:{level:"warn",color:Colors.YELLOW},ERROR:{level:"error",color:Colors.RED_BOLD}},isLogLevelAllowed=e=>{const r=["debug","info","warn","error"],o=getConfigValue("logLevel","info");return r.indexOf(e)>=r.indexOf(o)},isEnabled=()=>getConfigValue("loggingEnabled",!0);let remote=null;export const setRemoteLogger=e=>{remote=e};const safe=e=>{if(e)try{e()}catch{}},toErrorObject=e=>{if(e instanceof Error)return e;if("string"==typeof e)return new Error(e);try{return new Error(JSON.stringify(e))}catch{return new Error(String(e))}};let deviceContext=null;const normalizeAttributeValue=e=>null==e?null:"string"==typeof e||"number"==typeof e||"boolean"==typeof e?e:String(e);export const setDeviceContext=e=>{if(deviceContext=e,e)for(const[r,o]of Object.entries(e))safe(()=>remote?.setAttribute?.(r,normalizeAttributeValue(o)))};export const getDeviceContext=()=>deviceContext;const DEFAULT_LOGGER_OPTIONS={dedupeWindowMs:5e3,dedupeMaxPerWindow:1,screenHistoryLimit:20};let loggerOptions={...DEFAULT_LOGGER_OPTIONS};export const configureLogger=e=>{loggerOptions={...loggerOptions,...e},screenHistory.length>loggerOptions.screenHistoryLimit&&(screenHistory=screenHistory.slice(screenHistory.length-loggerOptions.screenHistoryLimit))};let currentScreen=null,screenHistory=[];const dedupeMap=new Map,MAX_DEDUPE_ENTRIES=200,dedupeKeyFor=e=>{const r=e.stack?.split("\n")[1]?.trim();return r?`${e.message}::${r}`:e.message},capDedupeMap=()=>{for(;dedupeMap.size>200;){const e=dedupeMap.keys().next().value;if(void 0===e)return;dedupeMap.delete(e)}},shouldForward=e=>{if(loggerOptions.dedupeWindowMs<=0)return{forward:!0};const r=Date.now(),o=dedupeMap.get(e);if(!o||r-o.windowStart>=loggerOptions.dedupeWindowMs){const t=o?.suppressedCount??0;return dedupeMap.set(e,{windowStart:r,count:1,suppressedCount:0}),capDedupeMap(),t>0?{forward:!0,suppressedCount:t}:{forward:!0}}return o.count+=1,o.count>loggerOptions.dedupeMaxPerWindow?(o.suppressedCount+=1,{forward:!1}):{forward:!0}},buildErrorContext=(e,r)=>{const o={...deviceContext};return null!==currentScreen&&(o.screen=currentScreen),r&&(o.suppressedCount=r),{...o,...e}},forwardErrorToRemote=(e,r)=>{const o=dedupeKeyFor(e),{forward:t,suppressedCount:n}=shouldForward(o);t&&safe(()=>remote?.recordError?.(e,buildErrorContext(r,n)))},logDebug=(e,...r)=>{isEnabled()&&isLogLevelAllowed(LogLevels.DEBUG.level)&&console.debug(`${LogLevels.DEBUG.color}[DEBUG]: ${redactString(e)}${Colors.RESET}`,...redact(r))},logInfo=(e,...r)=>{const o=redactString(e),t=redact(r);isEnabled()&&isLogLevelAllowed(LogLevels.INFO.level)&&(console.info(`${LogLevels.INFO.color}[INFO]: ${o}${Colors.RESET}`,...t),safe(()=>remote?.log?.("info",o,t)))},logWarning=(e,...r)=>{const o=redactString(e),t=redact(r);isEnabled()&&isLogLevelAllowed(LogLevels.WARN.level)&&(console.warn(`${LogLevels.WARN.color}[WARNING]: ${o}${Colors.RESET}`,...t),safe(()=>remote?.log?.("warn",o,t)))},logError=(e,...r)=>{const o=redactString(e),t=redact(r);isEnabled()&&isLogLevelAllowed(LogLevels.ERROR.level)&&console.error(`${LogLevels.ERROR.color}[ERROR]: ${o}${Colors.RESET}`,...t),safe(()=>remote?.log?.("error",o,t));const n=t[0];void 0!==n&&forwardErrorToRemote(toErrorObject(n),{source:"Logger.error",message:o})},breadcrumb=(e,r)=>{const o=redactString(e),t=redact(r);isEnabled()&&isLogLevelAllowed("info")&&console.info(`${Colors.BLUE}[BREADCRUMB]: ${o}${Colors.RESET}`,t??""),safe(()=>remote?.breadcrumb?.(o,t))},recordError=(e,r)=>{const o=redact(toErrorObject(e)),t=redact(r);isEnabled()&&isLogLevelAllowed("error")&&console.error(`${Colors.RED_BOLD}[RECORDED]: ${o.message}${Colors.RESET}`,t??""),forwardErrorToRemote(o,t)},asDict=e=>"object"==typeof e&&null!==e?e:null,headerValue=(e,r)=>{const o=asDict(e);if(!o)return null;const t="function"==typeof o.get?o.get.call(e,r):o[r]??o[r.toLowerCase()];return"string"==typeof t&&""!==t?t:null},requestFailed=(e,r={})=>{const{label:o,expectedStatuses:t=[],...n}=r,s=asDict(e),l=asDict(s?.config),c=asDict(s?.response),i=asDict(c?.data),d="number"==typeof c?.status?c.status:null,a="string"==typeof l?.method?l.method.toUpperCase():null,u="string"==typeof l?.url?l.url.split("?")[0]??null:null,g=o??([a,u].filter(Boolean).join(" ")||"Request");if("CanceledError"===s?.name||"ERR_CANCELED"===s?.code||!0===s?.__CANCEL__)return void logInfo(`${g} canceled`);if(null!==d&&t.includes(d))return void logInfo(`${g} refused (${d})`);const E="string"==typeof s?.message?s.message:e instanceof Error?e.message:null,f=redactString(null===d?`${g} failed`:`${g} failed (${d})`);isEnabled()&&isLogLevelAllowed(LogLevels.ERROR.level)&&console.error(`${LogLevels.ERROR.color}[ERROR]: ${f}${Colors.RESET}`,redactString(E??"")),safe(()=>remote?.log?.("error",f,[]));const p=getConfigValue("correlationIdHeader",null),L=redact({source:"Logger.requestFailed",label:g,status:d,method:a,path:u,code:"string"==typeof i?.code?i.code:null,traceId:"string"==typeof i?.traceId?i.traceId:null,correlationId:p?headerValue(l?.headers,p):null,detail:E,...n});forwardErrorToRemote(redact(new Error(f)),L)},setUser=(e,r)=>{safe(()=>remote?.setUser?.(e,r))},setAttribute=(e,r)=>{safe(()=>remote?.setAttribute?.(e,r))},trackScreen=(e,r)=>{e!==currentScreen&&(currentScreen=e,screenHistory.push(e),screenHistory.length>loggerOptions.screenHistoryLimit&&(screenHistory=screenHistory.slice(screenHistory.length-loggerOptions.screenHistoryLimit)),breadcrumb(`Screen: ${e}`,r))},getCurrentScreen=()=>currentScreen,getScreenHistory=()=>[...screenHistory];export const Logger={debug:logDebug,info:logInfo,warn:logWarning,error:logError,breadcrumb:breadcrumb,recordError:recordError,requestFailed:requestFailed,setUser:setUser,setAttribute:setAttribute,trackScreen:trackScreen,getCurrentScreen:getCurrentScreen,getScreenHistory:getScreenHistory};
@@ -1 +1 @@
1
- "use strict";export{disablePush,enablePush,initPush,notifyLocal,onAction,setBadgeCount,setCategories,setPushImplementation,usePush}from"./push.js";export{PUSH_STATUS}from"./status.js";
1
+ "use strict";export{disablePush,enablePush,initPush,notifyLocal,onAction,onReceive,setBadgeCount,setCategories,setPushImplementation,usePush}from"./push.js";export{PUSH_STATUS}from"./status.js";
@@ -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}}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,listening=!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?"denied":"unknown",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.notification.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.notification.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)},responseData=e=>({...triggerData(e),...contentData(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)}},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)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}};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;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()=>{requireConfig();const e=resolveModules();if(!e)return setState({status:"unavailable"}),"unavailable";const t=e.notifications;await channelsReady;let n=await t.getPermissionsAsync();if(n.granted||(n=await t.requestPermissionsAsync()),!n.granted)return setState({status:"denied"}),"denied";const o=await toDevice(await t.getDevicePushTokenAsync(),e.applicationId);return o?(await registerOnce(o),tokenSubscription?.remove(),tokenSubscription=t.addPushTokenListener(t=>{toDevice(t,e.applicationId).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 disablePush=async()=>{const e=requireConfig();tokenSubscription?.remove(),tokenSubscription=null,registeredKey=null,setState({enabled:!1}),await e.unregister(await e.getInstallationId())};export const setBadgeCount=async e=>{const t=resolveModules();t&&await t.notifications.setBadgeCountAsync(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 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?"denied":"unknown",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;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;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=>{toDevice(e,t).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 disablePush=async()=>{const e=requireConfig();tokenSubscription?.remove(),tokenSubscription=null,registeredKey=null,setState({enabled:!1}),await e.unregister(await e.getInstallationId())};export const setBadgeCount=async e=>{const t=resolveModules();t&&await t.notifications.setBadgeCountAsync(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,5 +1,5 @@
1
- export type { PushAction, PushActionHandler, PushAndroidChannel, PushCategory, PushCategoryAction, PushConfig, PushDevice, PushLocalNotification, PushModules, PushNotificationsLike, PushPlatform, PushState } from './push';
2
- export { disablePush, enablePush, initPush, notifyLocal, onAction, setBadgeCount, setCategories, setPushImplementation, usePush } from './push';
1
+ export type { EnablePushOptions, PushAction, PushActionHandler, PushAndroidChannel, PushApnsEnvironment, PushCategory, PushCategoryAction, PushConfig, PushDevice, PushLocalNotification, PushModules, PushNotificationsLike, PushPlatform, PushReceived, PushReceiveHandler, PushState } from './push';
2
+ export { disablePush, enablePush, initPush, notifyLocal, onAction, onReceive, 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
@@ -1,11 +1,37 @@
1
1
  import type { PushStatus } from './status';
2
2
  export type PushPlatform = 'Fcm' | 'Apns';
3
+ /** Which APNs gateway accepts this build's token: the sandbox for development signing, else production. */
4
+ export type PushApnsEnvironment = 'Development' | 'Production';
3
5
  export interface PushDevice {
4
6
  installationId: string;
5
7
  platform: PushPlatform;
6
8
  token: string;
7
9
  /** The app's bundle id, the APNs topic on iOS. */
8
10
  appId: string;
11
+ /**
12
+ * The APNs gateway this build's signing entitles, read from the app's
13
+ * `aps-environment`. Null on Android, and on iOS when it cannot be read
14
+ * (the simulator), so the server falls back to its own setting.
15
+ */
16
+ apnsEnvironment: PushApnsEnvironment | null;
17
+ }
18
+ /** A notification that arrived while the app was open. */
19
+ export interface PushReceived {
20
+ /** The notification's identifier on the phone, not a server id in `data`. */
21
+ notificationId: string;
22
+ title: string | null;
23
+ body: string | null;
24
+ data: Record<string, string>;
25
+ }
26
+ export type PushReceiveHandler = (notification: PushReceived) => void;
27
+ export interface EnablePushOptions {
28
+ /**
29
+ * Whether to show the system permission prompt when permission has not been
30
+ * granted yet. Defaults to true. With false, a phone already granted is
31
+ * registered silently and any other phone is left alone, so the app can ask
32
+ * later at a moment it chooses.
33
+ */
34
+ prompt?: boolean;
9
35
  }
10
36
  export interface PushAndroidChannel {
11
37
  id: string;
@@ -75,28 +101,31 @@ interface PermissionResponseLike {
75
101
  interface SubscriptionLike {
76
102
  remove: () => void;
77
103
  }
78
- interface NotificationResponseLike {
79
- actionIdentifier: string;
80
- userText?: string;
81
- notification: {
82
- request: {
83
- identifier: string;
84
- /** A background task receives the raw content, where data may still be a JSON `dataString`. */
85
- content: {
104
+ interface NotificationLike {
105
+ request: {
106
+ identifier: string;
107
+ /** A background task receives the raw content, where data may still be a JSON `dataString`. */
108
+ content: {
109
+ title?: string | null;
110
+ body?: string | null;
111
+ data?: Record<string, unknown> | null;
112
+ dataString?: string;
113
+ };
114
+ /** A remote push carries its raw payload: APNs `userInfo` on iOS, the FCM message on Android. */
115
+ trigger?: {
116
+ type?: string;
117
+ payload?: Record<string, unknown> | null;
118
+ remoteMessage?: {
86
119
  data?: Record<string, unknown> | null;
87
- dataString?: string;
88
- };
89
- /** A remote push carries its raw payload: APNs `userInfo` on iOS, the FCM message on Android. */
90
- trigger?: {
91
- type?: string;
92
- payload?: Record<string, unknown> | null;
93
- remoteMessage?: {
94
- data?: Record<string, unknown> | null;
95
- } | null;
96
120
  } | null;
97
- };
121
+ } | null;
98
122
  };
99
123
  }
124
+ interface NotificationResponseLike {
125
+ actionIdentifier: string;
126
+ userText?: string;
127
+ notification: NotificationLike;
128
+ }
100
129
  interface NotificationActionLike {
101
130
  identifier: string;
102
131
  buttonTitle: string;
@@ -121,6 +150,7 @@ export interface PushNotificationsLike {
121
150
  getDevicePushTokenAsync: () => Promise<DevicePushTokenLike>;
122
151
  addPushTokenListener: (listener: (token: DevicePushTokenLike) => void) => SubscriptionLike;
123
152
  addNotificationResponseReceivedListener: (listener: (response: NotificationResponseLike) => void) => SubscriptionLike;
153
+ addNotificationReceivedListener: (listener: (notification: NotificationLike) => void) => SubscriptionLike;
124
154
  getLastNotificationResponseAsync: () => Promise<NotificationResponseLike | null>;
125
155
  clearLastNotificationResponseAsync?: () => Promise<void>;
126
156
  setNotificationHandler: (handler: {
@@ -157,6 +187,8 @@ export interface PushModules {
157
187
  notifications: PushNotificationsLike;
158
188
  /** The bundle id, from expo-application. */
159
189
  applicationId: string | null;
190
+ /** expo-application's `getIosPushNotificationServiceEnvironmentAsync`; iOS only. */
191
+ getApnsEnvironment: () => Promise<'development' | 'production' | null>;
160
192
  }
161
193
  /**
162
194
  * Replace the native modules. For tests and the showcase, to reach paths a
@@ -170,6 +202,7 @@ export declare const setPushImplementation: (impl: PushModules | null) => void;
170
202
  * import throws on Android. Exported for `./task`; not on the `/push` entry.
171
203
  */
172
204
  export declare const resolveModules: () => PushModules | null;
205
+ export declare const toPushReceived: (notification: NotificationLike) => PushReceived;
173
206
  export declare const toPushAction: (response: NotificationResponseLike) => PushAction;
174
207
  export declare const toNotificationActions: (actions: PushCategoryAction[]) => NotificationActionLike[];
175
208
  /** The payload of the background notification task: routes button taps, ignores received pushes. */
@@ -183,8 +216,11 @@ export declare const initPush: (next: PushConfig) => void;
183
216
  /**
184
217
  * Asks for permission, reads the native device token and registers it through
185
218
  * `config.register`, then re-registers whenever the OS rotates the token.
219
+ * With `{ prompt: false }` it never shows the permission prompt: a phone
220
+ * already granted registers, and any other resolves with its status
221
+ * (`unknown` when the person has not been asked yet, `denied` when refused).
186
222
  */
187
- export declare const enablePush: () => Promise<"granted" | "denied" | "unavailable">;
223
+ export declare const enablePush: ({ prompt }?: EnablePushOptions) => Promise<PushStatus>;
188
224
  /**
189
225
  * Unregisters the device through `config.unregister` and stops following token
190
226
  * changes. Taps on notifications already delivered still reach `onOpen`.
@@ -204,6 +240,13 @@ export declare const setCategories: (categories: PushCategory[]) => Promise<void
204
240
  * `/push/task`, which sets this handler too. Returns a function that removes it.
205
241
  */
206
242
  export declare const onAction: (handler: PushActionHandler) => (() => void);
243
+ /**
244
+ * Calls `handler` for each notification that arrives while the app is open,
245
+ * whether or not it is shown, so the app can refresh what it displays. Not
246
+ * called for a notification that arrives in the background; its tap still
247
+ * goes to `onOpen`. Returns a function that removes the handler.
248
+ */
249
+ export declare const onReceive: (handler: PushReceiveHandler) => (() => void);
207
250
  /** Shows a notification from the phone itself, for example when a button's request failed. */
208
251
  export declare const notifyLocal: ({ title, body, data }: PushLocalNotification) => Promise<void>;
209
252
  /** Push status for a settings switch, re-rendering when it changes. */
@@ -1,5 +1,5 @@
1
- export type { PushAction, PushActionHandler, PushAndroidChannel, PushCategory, PushCategoryAction, PushConfig, PushDevice, PushLocalNotification, PushModules, PushNotificationsLike, PushPlatform, PushState } from './push.js';
2
- export { disablePush, enablePush, initPush, notifyLocal, onAction, setBadgeCount, setCategories, setPushImplementation, usePush } from './push.js';
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, enablePush, initPush, notifyLocal, onAction, onReceive, 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
@@ -1,11 +1,37 @@
1
1
  import type { PushStatus } from './status.js';
2
2
  export type PushPlatform = 'Fcm' | 'Apns';
3
+ /** Which APNs gateway accepts this build's token: the sandbox for development signing, else production. */
4
+ export type PushApnsEnvironment = 'Development' | 'Production';
3
5
  export interface PushDevice {
4
6
  installationId: string;
5
7
  platform: PushPlatform;
6
8
  token: string;
7
9
  /** The app's bundle id, the APNs topic on iOS. */
8
10
  appId: string;
11
+ /**
12
+ * The APNs gateway this build's signing entitles, read from the app's
13
+ * `aps-environment`. Null on Android, and on iOS when it cannot be read
14
+ * (the simulator), so the server falls back to its own setting.
15
+ */
16
+ apnsEnvironment: PushApnsEnvironment | null;
17
+ }
18
+ /** A notification that arrived while the app was open. */
19
+ export interface PushReceived {
20
+ /** The notification's identifier on the phone, not a server id in `data`. */
21
+ notificationId: string;
22
+ title: string | null;
23
+ body: string | null;
24
+ data: Record<string, string>;
25
+ }
26
+ export type PushReceiveHandler = (notification: PushReceived) => void;
27
+ export interface EnablePushOptions {
28
+ /**
29
+ * Whether to show the system permission prompt when permission has not been
30
+ * granted yet. Defaults to true. With false, a phone already granted is
31
+ * registered silently and any other phone is left alone, so the app can ask
32
+ * later at a moment it chooses.
33
+ */
34
+ prompt?: boolean;
9
35
  }
10
36
  export interface PushAndroidChannel {
11
37
  id: string;
@@ -75,28 +101,31 @@ interface PermissionResponseLike {
75
101
  interface SubscriptionLike {
76
102
  remove: () => void;
77
103
  }
78
- interface NotificationResponseLike {
79
- actionIdentifier: string;
80
- userText?: string;
81
- notification: {
82
- request: {
83
- identifier: string;
84
- /** A background task receives the raw content, where data may still be a JSON `dataString`. */
85
- content: {
104
+ interface NotificationLike {
105
+ request: {
106
+ identifier: string;
107
+ /** A background task receives the raw content, where data may still be a JSON `dataString`. */
108
+ content: {
109
+ title?: string | null;
110
+ body?: string | null;
111
+ data?: Record<string, unknown> | null;
112
+ dataString?: string;
113
+ };
114
+ /** A remote push carries its raw payload: APNs `userInfo` on iOS, the FCM message on Android. */
115
+ trigger?: {
116
+ type?: string;
117
+ payload?: Record<string, unknown> | null;
118
+ remoteMessage?: {
86
119
  data?: Record<string, unknown> | null;
87
- dataString?: string;
88
- };
89
- /** A remote push carries its raw payload: APNs `userInfo` on iOS, the FCM message on Android. */
90
- trigger?: {
91
- type?: string;
92
- payload?: Record<string, unknown> | null;
93
- remoteMessage?: {
94
- data?: Record<string, unknown> | null;
95
- } | null;
96
120
  } | null;
97
- };
121
+ } | null;
98
122
  };
99
123
  }
124
+ interface NotificationResponseLike {
125
+ actionIdentifier: string;
126
+ userText?: string;
127
+ notification: NotificationLike;
128
+ }
100
129
  interface NotificationActionLike {
101
130
  identifier: string;
102
131
  buttonTitle: string;
@@ -121,6 +150,7 @@ export interface PushNotificationsLike {
121
150
  getDevicePushTokenAsync: () => Promise<DevicePushTokenLike>;
122
151
  addPushTokenListener: (listener: (token: DevicePushTokenLike) => void) => SubscriptionLike;
123
152
  addNotificationResponseReceivedListener: (listener: (response: NotificationResponseLike) => void) => SubscriptionLike;
153
+ addNotificationReceivedListener: (listener: (notification: NotificationLike) => void) => SubscriptionLike;
124
154
  getLastNotificationResponseAsync: () => Promise<NotificationResponseLike | null>;
125
155
  clearLastNotificationResponseAsync?: () => Promise<void>;
126
156
  setNotificationHandler: (handler: {
@@ -157,6 +187,8 @@ export interface PushModules {
157
187
  notifications: PushNotificationsLike;
158
188
  /** The bundle id, from expo-application. */
159
189
  applicationId: string | null;
190
+ /** expo-application's `getIosPushNotificationServiceEnvironmentAsync`; iOS only. */
191
+ getApnsEnvironment: () => Promise<'development' | 'production' | null>;
160
192
  }
161
193
  /**
162
194
  * Replace the native modules. For tests and the showcase, to reach paths a
@@ -170,6 +202,7 @@ export declare const setPushImplementation: (impl: PushModules | null) => void;
170
202
  * import throws on Android. Exported for `./task`; not on the `/push` entry.
171
203
  */
172
204
  export declare const resolveModules: () => PushModules | null;
205
+ export declare const toPushReceived: (notification: NotificationLike) => PushReceived;
173
206
  export declare const toPushAction: (response: NotificationResponseLike) => PushAction;
174
207
  export declare const toNotificationActions: (actions: PushCategoryAction[]) => NotificationActionLike[];
175
208
  /** The payload of the background notification task: routes button taps, ignores received pushes. */
@@ -183,8 +216,11 @@ export declare const initPush: (next: PushConfig) => void;
183
216
  /**
184
217
  * Asks for permission, reads the native device token and registers it through
185
218
  * `config.register`, then re-registers whenever the OS rotates the token.
219
+ * With `{ prompt: false }` it never shows the permission prompt: a phone
220
+ * already granted registers, and any other resolves with its status
221
+ * (`unknown` when the person has not been asked yet, `denied` when refused).
186
222
  */
187
- export declare const enablePush: () => Promise<"granted" | "denied" | "unavailable">;
223
+ export declare const enablePush: ({ prompt }?: EnablePushOptions) => Promise<PushStatus>;
188
224
  /**
189
225
  * Unregisters the device through `config.unregister` and stops following token
190
226
  * changes. Taps on notifications already delivered still reach `onOpen`.
@@ -204,6 +240,13 @@ export declare const setCategories: (categories: PushCategory[]) => Promise<void
204
240
  * `/push/task`, which sets this handler too. Returns a function that removes it.
205
241
  */
206
242
  export declare const onAction: (handler: PushActionHandler) => (() => void);
243
+ /**
244
+ * Calls `handler` for each notification that arrives while the app is open,
245
+ * whether or not it is shown, so the app can refresh what it displays. Not
246
+ * called for a notification that arrives in the background; its tap still
247
+ * goes to `onOpen`. Returns a function that removes the handler.
248
+ */
249
+ export declare const onReceive: (handler: PushReceiveHandler) => (() => void);
207
250
  /** Shows a notification from the phone itself, for example when a button's request failed. */
208
251
  export declare const notifyLocal: ({ title, body, data }: PushLocalNotification) => Promise<void>;
209
252
  /** Push status for a settings switch, re-rendering when it changes. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webority/mobile-core",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Platform layer for Webority React Native apps: HTTP clients, auth/token storage, config, logging, formatters, validators, network status, permissions, storage and version checks. No UI.",
5
5
  "keywords": [
6
6
  "react-native",