@webority/mobile-core 0.0.26 → 0.0.27

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.
Files changed (39) hide show
  1. package/lib/commonjs/api/retry.js +17 -1
  2. package/lib/commonjs/api/userClient.js +1 -1
  3. package/lib/commonjs/auth/authStore.js +49 -16
  4. package/lib/commonjs/config/index.js +8 -2
  5. package/lib/commonjs/formatters/currency.js +2 -1
  6. package/lib/commonjs/formatters/date.js +5 -6
  7. package/lib/commonjs/formatters/dateMath.js +2 -1
  8. package/lib/commonjs/formatters/intlCache.js +37 -0
  9. package/lib/commonjs/formatters/number.js +4 -3
  10. package/lib/commonjs/index.js +7 -0
  11. package/lib/commonjs/initSDK.js +1 -1
  12. package/lib/commonjs/network/networkStatus.js +40 -2
  13. package/lib/commonjs/utils/base64.js +24 -0
  14. package/lib/module/api/retry.js +17 -1
  15. package/lib/module/api/userClient.js +2 -2
  16. package/lib/module/auth/authStore.js +49 -16
  17. package/lib/module/config/index.js +7 -1
  18. package/lib/module/formatters/currency.js +2 -1
  19. package/lib/module/formatters/date.js +5 -6
  20. package/lib/module/formatters/dateMath.js +2 -1
  21. package/lib/module/formatters/intlCache.js +30 -0
  22. package/lib/module/formatters/number.js +4 -3
  23. package/lib/module/index.js +2 -0
  24. package/lib/module/initSDK.js +2 -2
  25. package/lib/module/network/networkStatus.js +37 -0
  26. package/lib/module/utils/base64.js +19 -0
  27. package/lib/typescript/commonjs/config/index.d.ts +6 -0
  28. package/lib/typescript/commonjs/formatters/intlCache.d.ts +6 -0
  29. package/lib/typescript/commonjs/index.d.ts +1 -0
  30. package/lib/typescript/commonjs/initSDK.d.ts +1 -1
  31. package/lib/typescript/commonjs/network/networkStatus.d.ts +6 -0
  32. package/lib/typescript/commonjs/utils/base64.d.ts +7 -0
  33. package/lib/typescript/module/config/index.d.ts +6 -0
  34. package/lib/typescript/module/formatters/intlCache.d.ts +6 -0
  35. package/lib/typescript/module/index.d.ts +1 -0
  36. package/lib/typescript/module/initSDK.d.ts +1 -1
  37. package/lib/typescript/module/network/networkStatus.d.ts +6 -0
  38. package/lib/typescript/module/utils/base64.d.ts +7 -0
  39. package/package.json +1 -1
@@ -16,10 +16,26 @@ exports.isRetryableStatus = exports.attachRetry = void 0;
16
16
  */
17
17
  const isRetryableStatus = status => typeof status === 'number' && (status === 429 || status >= 500 && status <= 599);
18
18
  exports.isRetryableStatus = isRetryableStatus;
19
+ const IDEMPOTENT_METHODS = new Set(['get', 'head', 'options', 'put', 'delete']);
20
+
21
+ /**
22
+ * The same reasoning reaches status codes on a write. A 500 on a POST or PATCH
23
+ * can follow a partial commit, so replaying it can duplicate the record; only
24
+ * 429 and 503 say the server refused the request before doing the work. A
25
+ * request that knows its write is safe to repeat passes its own `shouldRetry`.
26
+ */
27
+ const isRetryableFailure = error => {
28
+ const status = error.response?.status;
29
+ if (!isRetryableStatus(status)) {
30
+ return false;
31
+ }
32
+ const method = (error.config?.method ?? 'get').toLowerCase();
33
+ return IDEMPOTENT_METHODS.has(method) || status === 429 || status === 503;
34
+ };
19
35
  const DEFAULT_POLICY = {
20
36
  retries: 3,
21
37
  retryDelayMs: attempt => attempt <= 1 ? 0 : 2 ** attempt * 1000,
22
- shouldRetry: error => isRetryableStatus(error.response?.status),
38
+ shouldRetry: isRetryableFailure,
23
39
  onRetry: () => undefined
24
40
  };
25
41
  const waitOrAbort = (delay, signal) => new Promise(resolve => {
@@ -157,7 +157,7 @@ userClient.interceptors.request.use(async config => {
157
157
  // Reject before any network call when the device is known to be offline.
158
158
  // Unknown connectivity (netinfo not installed, or undetermined) is treated
159
159
  // as online — never block a request on an unavailable probe.
160
- const network = await (0, _networkStatus.getNetworkStatus)();
160
+ const network = await (0, _networkStatus.getLastKnownNetworkStatus)();
161
161
  if (network.isConnected === false) {
162
162
  _index2.Logger.warn(`Request blocked: device is offline. URL: ${config.url}`);
163
163
  return Promise.reject(new RequestOfflineError());
@@ -87,6 +87,24 @@ const expoSecureStoreBackend = {
87
87
  }
88
88
  };
89
89
  const resolveBackend = () => backend ?? expoSecureStoreBackend;
90
+
91
+ /**
92
+ * Every request reads the access token, and a secure-store read is a native
93
+ * keychain/keystore decrypt. Reads are therefore served from memory, written
94
+ * through by `storeToken` and dropped by `removeToken`. `generation` moves on
95
+ * every write, removal and backend swap, so a read that started before one of
96
+ * those cannot cache the value it saw.
97
+ */
98
+ const memo = new Map();
99
+ let generation = 0;
100
+ const forget = key => {
101
+ generation += 1;
102
+ if (key === undefined) {
103
+ memo.clear();
104
+ } else {
105
+ memo.delete(key);
106
+ }
107
+ };
90
108
  const requireBackend = op => {
91
109
  const b = resolveBackend();
92
110
  if (!b) {
@@ -104,6 +122,7 @@ const requireBackend = op => {
104
122
  */
105
123
  const setKeychainImplementation = impl => {
106
124
  backend = impl;
125
+ forget();
107
126
  };
108
127
 
109
128
  /**
@@ -114,6 +133,7 @@ const storeToken = async (key, token, expiry) => {
114
133
  // Never log the token or expiry values: Logger forwards to whatever remote
115
134
  // sink the app wired (Crashlytics/Sentry), so a token here leaves the device.
116
135
  _index.Logger.info(`Storing token for key: ${key}`);
136
+ forget(key);
117
137
  try {
118
138
  const data = JSON.stringify({
119
139
  token,
@@ -122,8 +142,14 @@ const storeToken = async (key, token, expiry) => {
122
142
  await requireBackend('store a token').setGenericPassword('token', data, {
123
143
  service: key
124
144
  });
145
+ forget(key);
146
+ memo.set(key, {
147
+ token,
148
+ expiry
149
+ });
125
150
  _index.Logger.info(`Token stored successfully for key: ${key}`);
126
151
  } catch (error) {
152
+ forget(key);
127
153
  // Log AND rethrow. Swallowing this told the caller the token was persisted
128
154
  // when it was not, so an app would show a signed-in user holding a token
129
155
  // that never reached the keychain — the session then dies at the next
@@ -162,6 +188,10 @@ const parseTokenData = credentials => {
162
188
  * Retrieve token and check if expired.
163
189
  */
164
190
  const getToken = async key => {
191
+ if (memo.has(key)) {
192
+ return memo.get(key) ?? null;
193
+ }
194
+ const seen = generation;
165
195
  try {
166
196
  const credentials = await requireBackend('read a token').getGenericPassword({
167
197
  service: key
@@ -170,24 +200,24 @@ const getToken = async key => {
170
200
  // token, and every authenticated request, image and download asks for one.
171
201
  // Warning here put three info lines and a warning into the log, and into the
172
202
  // remote crash sink, on every one of those calls.
173
- if (!credentials) {
174
- return null;
203
+ let value = null;
204
+ if (credentials) {
205
+ const parsedData = parseTokenData(credentials);
206
+ if (parsedData) {
207
+ value = {
208
+ token: parsedData.token,
209
+ expiry: parsedData.expiry
210
+ };
211
+ } else {
212
+ // Something IS stored and it is not a token. That is corruption worth
213
+ // hearing about, and it is a different event from having no token.
214
+ _index.Logger.warn(`Stored credentials for key "${key}" are not a valid token; ignoring them.`);
215
+ }
175
216
  }
176
- const parsedData = parseTokenData(credentials);
177
- if (!parsedData) {
178
- // Something IS stored and it is not a token. That is corruption worth
179
- // hearing about, and it is a different event from having no token.
180
- _index.Logger.warn(`Stored credentials for key "${key}" are not a valid token; ignoring them.`);
181
- return null;
217
+ if (seen === generation) {
218
+ memo.set(key, value);
182
219
  }
183
- const {
184
- token,
185
- expiry
186
- } = parsedData;
187
- return {
188
- token,
189
- expiry
190
- };
220
+ return value;
191
221
  } catch (error) {
192
222
  const message = error instanceof Error ? error.message : String(error);
193
223
  // Both spellings matter. Android throws "Key permanently invalidated" when
@@ -211,6 +241,7 @@ const getToken = async key => {
211
241
  */
212
242
  exports.getToken = getToken;
213
243
  const removeToken = async key => {
244
+ forget(key);
214
245
  try {
215
246
  await requireBackend('remove a token').resetGenericPassword({
216
247
  service: key
@@ -226,6 +257,8 @@ const removeToken = async key => {
226
257
  return;
227
258
  }
228
259
  _index.Logger.error(`Failed to remove token for key: ${key}`, error);
260
+ } finally {
261
+ forget(key);
229
262
  }
230
263
  };
231
264
  exports.removeToken = removeToken;
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.getConfig = void 0;
6
+ exports.getConfig = exports.DEFAULT_LOG_LEVEL = void 0;
7
7
  exports.getConfigValue = getConfigValue;
8
8
  exports.setConfig = exports.resetConfig = void 0;
9
9
  /**
@@ -11,10 +11,16 @@ exports.setConfig = exports.resetConfig = void 0;
11
11
  * Provides a flexible way to inject configuration without hard dependencies
12
12
  */
13
13
 
14
+ /**
15
+ * Info lines (each token refresh, each unauthorised URL) reach the device log
16
+ * and any remote sink, so a release build that sets nothing logs warnings and
17
+ * errors only.
18
+ */
19
+ const DEFAULT_LOG_LEVEL = exports.DEFAULT_LOG_LEVEL = __DEV__ ? 'info' : 'warn';
14
20
  const DEFAULT_CONFIG = {
15
21
  baseUrl: null,
16
22
  loggingEnabled: true,
17
- logLevel: 'info',
23
+ logLevel: DEFAULT_LOG_LEVEL,
18
24
  environment: 'Development',
19
25
  country: 'in',
20
26
  appId: null,
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.formatCurrency = void 0;
7
+ var _intlCache = require("./intlCache.js");
7
8
  const hasIntlNumber = () => typeof Intl !== 'undefined' && typeof Intl.NumberFormat === 'function';
8
9
  const indianGroupingFallback = (n, fractionDigits) => {
9
10
  // Indian numbering: 1,23,45,678.90 (last 3 digits, then groups of 2).
@@ -41,7 +42,7 @@ const formatCurrency = (amount, options = {}) => {
41
42
  const minFrac = minimumFractionDigits ?? (Number.isInteger(amount) ? 0 : 2);
42
43
  if (hasIntlNumber()) {
43
44
  try {
44
- const formatted = new Intl.NumberFormat(locale, {
45
+ const formatted = (0, _intlCache.numberFormat)(locale, {
45
46
  style: 'currency',
46
47
  currency,
47
48
  minimumFractionDigits: minFrac,
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.formatTime = exports.formatRelativeTime = exports.formatDateTime = exports.formatDate = void 0;
7
+ var _intlCache = require("./intlCache.js");
7
8
  const toDate = value => {
8
9
  if (value instanceof Date) {
9
10
  return Number.isNaN(value.getTime()) ? null : value;
@@ -52,7 +53,7 @@ const formatDate = (value, style = 'medium', locale = 'en-IN') => {
52
53
  }
53
54
  if (hasIntl()) {
54
55
  try {
55
- return new Intl.DateTimeFormat(locale, {
56
+ return (0, _intlCache.dateTimeFormat)(locale, {
56
57
  dateStyle: style
57
58
  }).format(d);
58
59
  } catch {
@@ -73,7 +74,7 @@ const formatTime = (value, style = 'short', locale = 'en-IN') => {
73
74
  }
74
75
  if (hasIntl()) {
75
76
  try {
76
- return new Intl.DateTimeFormat(locale, {
77
+ return (0, _intlCache.dateTimeFormat)(locale, {
77
78
  timeStyle: style
78
79
  }).format(d);
79
80
  } catch {
@@ -94,7 +95,7 @@ const formatDateTime = (value, dateStyle = 'medium', timeStyle = 'short', locale
94
95
  }
95
96
  if (hasIntl()) {
96
97
  try {
97
- return new Intl.DateTimeFormat(locale, {
98
+ return (0, _intlCache.dateTimeFormat)(locale, {
98
99
  dateStyle,
99
100
  timeStyle
100
101
  }).format(d);
@@ -152,9 +153,7 @@ const formatRelativeTime = (value, base = new Date(), locale = 'en-IN') => {
152
153
  const Rtf = hasIntl() ? Intl.RelativeTimeFormat : undefined;
153
154
  if (typeof Rtf === 'function') {
154
155
  try {
155
- return new Rtf(locale, {
156
- numeric: 'auto'
157
- }).format(amount, unit);
156
+ return (0, _intlCache.relativeTimeFormat)(Rtf, locale).format(amount, unit);
158
157
  } catch {
159
158
  /* fall through */
160
159
  }
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.startOfDay = exports.isSameDay = exports.isDateBefore = exports.isDateAfter = exports.formatTimeInZone = exports.formatTimeInWindowsZone = exports.formatTime12h = exports.formatDatePattern = exports.formatDateISO = exports.formatDateDDMMMYYYY = exports.diffMs = exports.addDays = void 0;
7
+ var _intlCache = require("./intlCache.js");
7
8
  var _windowsToIanaTable = require("./windowsToIanaTable.js");
8
9
  const toDate = value => {
9
10
  if (value instanceof Date) {
@@ -277,7 +278,7 @@ const formatTimeInZone = (value, ianaTimeZone) => {
277
278
  // answer available, not a genuine timezone conversion.
278
279
  return formatTime12h(d);
279
280
  }
280
- const parts = new Intl.DateTimeFormat('en-US', {
281
+ const parts = (0, _intlCache.dateTimeFormat)('en-US', {
281
282
  timeZone: ianaTimeZone,
282
283
  hour: 'numeric',
283
284
  minute: '2-digit',
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.relativeTimeFormat = exports.numberFormat = exports.dateTimeFormat = void 0;
7
+ /**
8
+ * Building an Intl formatter costs far more than using one (and on Hermes for
9
+ * Android it goes through ICU over JNI), while list rows and slider frames call
10
+ * the formatters many times with the same arguments. Formatters are therefore
11
+ * built once per locale and options. The limit only guards against unbounded
12
+ * input such as arbitrary time zones; real apps use a handful of keys.
13
+ */
14
+ const LIMIT = 100;
15
+ const cached = (cache, key, create) => {
16
+ let formatter = cache.get(key);
17
+ if (formatter === undefined) {
18
+ formatter = create();
19
+ if (cache.size >= LIMIT) {
20
+ cache.clear();
21
+ }
22
+ cache.set(key, formatter);
23
+ }
24
+ return formatter;
25
+ };
26
+ const numberFormats = new Map();
27
+ const dateTimeFormats = new Map();
28
+ const relativeTimeFormats = new Map();
29
+ const numberFormat = (locale, options = {}) => cached(numberFormats, `${locale}|${JSON.stringify(options)}`, () => new Intl.NumberFormat(locale, options));
30
+ exports.numberFormat = numberFormat;
31
+ const dateTimeFormat = (locale, options) => cached(dateTimeFormats, `${locale}|${JSON.stringify(options)}`, () => new Intl.DateTimeFormat(locale, options));
32
+ exports.dateTimeFormat = dateTimeFormat;
33
+ const relativeTimeFormat = (RelativeTimeFormat, locale) => cached(relativeTimeFormats, locale, () => new RelativeTimeFormat(locale, {
34
+ numeric: 'auto'
35
+ }));
36
+ exports.relativeTimeFormat = relativeTimeFormat;
37
+ //# sourceMappingURL=intlCache.js.map
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.formatPercent = exports.formatNumber = exports.formatCompactNumber = void 0;
7
+ var _intlCache = require("./intlCache.js");
7
8
  const hasIntlNumber = () => typeof Intl !== 'undefined' && typeof Intl.NumberFormat === 'function';
8
9
 
9
10
  /**
@@ -20,7 +21,7 @@ const formatNumber = (value, fractionDigits, locale = 'en-IN') => {
20
21
  opts.minimumFractionDigits = fractionDigits;
21
22
  opts.maximumFractionDigits = fractionDigits;
22
23
  }
23
- return new Intl.NumberFormat(locale, opts).format(value);
24
+ return (0, _intlCache.numberFormat)(locale, opts).format(value);
24
25
  } catch {
25
26
  /* fall through */
26
27
  }
@@ -40,7 +41,7 @@ const formatPercent = (value, fractionDigits = 0, asRatio = true, locale = 'en-I
40
41
  const ratio = asRatio ? value : value / 100;
41
42
  if (hasIntlNumber()) {
42
43
  try {
43
- return new Intl.NumberFormat(locale, {
44
+ return (0, _intlCache.numberFormat)(locale, {
44
45
  style: 'percent',
45
46
  minimumFractionDigits: fractionDigits,
46
47
  maximumFractionDigits: fractionDigits
@@ -63,7 +64,7 @@ const formatCompactNumber = (value, locale = 'en-IN') => {
63
64
  }
64
65
  if (hasIntlNumber()) {
65
66
  try {
66
- return new Intl.NumberFormat(locale, {
67
+ return (0, _intlCache.numberFormat)(locale, {
67
68
  notation: 'compact',
68
69
  maximumFractionDigits: 1
69
70
  }).format(value);
@@ -117,6 +117,12 @@ Object.defineProperty(exports, "diffMs", {
117
117
  return _index3.diffMs;
118
118
  }
119
119
  });
120
+ Object.defineProperty(exports, "encodeBase64", {
121
+ enumerable: true,
122
+ get: function () {
123
+ return _base.encodeBase64;
124
+ }
125
+ });
120
126
  Object.defineProperty(exports, "formatCompactNumber", {
121
127
  enumerable: true,
122
128
  get: function () {
@@ -630,6 +636,7 @@ var _initUserAuth = require("./initUserAuth.js");
630
636
  var _index4 = require("./logger/index.js");
631
637
  var _index5 = require("./network/index.js");
632
638
  var _index6 = require("./storage/index.js");
639
+ var _base = require("./utils/base64.js");
633
640
  var _index7 = require("./validators/index.js");
634
641
  var _index8 = require("./versionCheck/index.js");
635
642
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
@@ -39,7 +39,7 @@ const initWebority = config => {
39
39
  (0, _index.setConfig)({
40
40
  baseUrl: config.baseUrl,
41
41
  loggingEnabled: config.loggingEnabled ?? true,
42
- logLevel: config.logLevel || 'info',
42
+ logLevel: config.logLevel || _index.DEFAULT_LOG_LEVEL,
43
43
  environment: config.environment || 'Development',
44
44
  country: (config.country || 'in').toLowerCase(),
45
45
  appId: config.appId ?? null,
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.useNetworkStatus = exports.setNetworkStatusImplementation = exports.getNetworkStatus = exports.addNetworkStatusListener = void 0;
6
+ exports.useNetworkStatus = exports.setNetworkStatusImplementation = exports.getNetworkStatus = exports.getLastKnownNetworkStatus = exports.addNetworkStatusListener = void 0;
7
7
  var ExpoNetwork = _interopRequireWildcard(require("expo-network"));
8
8
  var _react = require("react");
9
9
  var _index = require("../logger/index.js");
@@ -23,6 +23,13 @@ const DEFAULT_STATUS = {
23
23
  * One backend also means one behaviour, rather than each app choosing.
24
24
  */
25
25
  let override = null;
26
+ let lastKnown = null;
27
+ let stopFollowing = null;
28
+ const stopFollowingNetwork = () => {
29
+ stopFollowing?.();
30
+ stopFollowing = null;
31
+ lastKnown = null;
32
+ };
26
33
 
27
34
  /**
28
35
  * Replace the backend. Intended for TESTS and for the showcase, which uses it
@@ -31,6 +38,7 @@ let override = null;
31
38
  */
32
39
  const setNetworkStatusImplementation = impl => {
33
40
  override = impl;
41
+ stopFollowingNetwork();
34
42
  };
35
43
  exports.setNetworkStatusImplementation = setNetworkStatusImplementation;
36
44
  const resolveNetInfo = () => override ?? expoNetworkBackend;
@@ -103,11 +111,41 @@ const addNetworkStatusListener = listener => {
103
111
  return ni.addEventListener(state => listener(normalize(state)));
104
112
  };
105
113
 
114
+ /**
115
+ * The connectivity the HTTP client checks before every request. One listener,
116
+ * attached on first use, keeps it current, so a request costs no native probe:
117
+ * one probe seeds the value and the listener's events replace it.
118
+ */
119
+ exports.addNetworkStatusListener = addNetworkStatusListener;
120
+ const getLastKnownNetworkStatus = async () => {
121
+ if (!stopFollowing) {
122
+ try {
123
+ stopFollowing = addNetworkStatusListener(status => {
124
+ lastKnown = status;
125
+ });
126
+ } catch (error) {
127
+ _index.Logger.error('[network] could not follow network changes', error);
128
+ return getNetworkStatus();
129
+ }
130
+ }
131
+ if (lastKnown) {
132
+ return lastKnown;
133
+ }
134
+ const following = stopFollowing;
135
+ const probed = await getNetworkStatus();
136
+ if (stopFollowing !== following) {
137
+ return probed;
138
+ }
139
+ // An event that arrived during the probe is newer than the probe.
140
+ lastKnown ??= probed;
141
+ return lastKnown;
142
+ };
143
+
106
144
  /**
107
145
  * React hook that returns the current network status and re-renders on change.
108
146
  * Reports an optimistic online default when NetInfo is not installed.
109
147
  */
110
- exports.addNetworkStatusListener = addNetworkStatusListener;
148
+ exports.getLastKnownNetworkStatus = getLastKnownNetworkStatus;
111
149
  const useNetworkStatus = () => {
112
150
  const [status, setStatus] = (0, _react.useState)(DEFAULT_STATUS);
113
151
  (0, _react.useEffect)(() => {
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.encodeBase64 = void 0;
7
+ // Small enough for every engine's argument limit in `String.fromCharCode.apply`.
8
+ const CHUNK = 0x2000;
9
+
10
+ /**
11
+ * Base64 of raw bytes, for a `data:` URI. Builds a Latin-1 string a chunk at a
12
+ * time and hands it to the engine's native `btoa` (Hermes ships it), which is
13
+ * many times faster on the JS thread than encoding byte by byte in JS.
14
+ */
15
+ const encodeBase64 = buffer => {
16
+ const bytes = new Uint8Array(buffer);
17
+ let binary = '';
18
+ for (let i = 0; i < bytes.length; i += CHUNK) {
19
+ binary += String.fromCharCode.apply(null, bytes.subarray(i, i + CHUNK));
20
+ }
21
+ return btoa(binary);
22
+ };
23
+ exports.encodeBase64 = encodeBase64;
24
+ //# sourceMappingURL=base64.js.map
@@ -11,10 +11,26 @@
11
11
  * as readily as GETs.
12
12
  */
13
13
  export const isRetryableStatus = status => typeof status === 'number' && (status === 429 || status >= 500 && status <= 599);
14
+ const IDEMPOTENT_METHODS = new Set(['get', 'head', 'options', 'put', 'delete']);
15
+
16
+ /**
17
+ * The same reasoning reaches status codes on a write. A 500 on a POST or PATCH
18
+ * can follow a partial commit, so replaying it can duplicate the record; only
19
+ * 429 and 503 say the server refused the request before doing the work. A
20
+ * request that knows its write is safe to repeat passes its own `shouldRetry`.
21
+ */
22
+ const isRetryableFailure = error => {
23
+ const status = error.response?.status;
24
+ if (!isRetryableStatus(status)) {
25
+ return false;
26
+ }
27
+ const method = (error.config?.method ?? 'get').toLowerCase();
28
+ return IDEMPOTENT_METHODS.has(method) || status === 429 || status === 503;
29
+ };
14
30
  const DEFAULT_POLICY = {
15
31
  retries: 3,
16
32
  retryDelayMs: attempt => attempt <= 1 ? 0 : 2 ** attempt * 1000,
17
- shouldRetry: error => isRetryableStatus(error.response?.status),
33
+ shouldRetry: isRetryableFailure,
18
34
  onRetry: () => undefined
19
35
  };
20
36
  const waitOrAbort = (delay, signal) => new Promise(resolve => {
@@ -4,7 +4,7 @@ import axios, { AxiosHeaders } from 'axios';
4
4
  import { ACCESS_TOKEN_KEY, getToken } from "../auth/authStore.js";
5
5
  import { getConfigValue } from "../config/index.js";
6
6
  import { Logger } from "../logger/index.js";
7
- import { getNetworkStatus } from "../network/networkStatus.js";
7
+ import { getLastKnownNetworkStatus } from "../network/networkStatus.js";
8
8
  import "../types/axios.js";
9
9
  import { applyConfiguredBaseUrl } from "./baseUrl.js";
10
10
  import { logCurl } from "./curl.js";
@@ -135,7 +135,7 @@ userClient.interceptors.request.use(async config => {
135
135
  // Reject before any network call when the device is known to be offline.
136
136
  // Unknown connectivity (netinfo not installed, or undetermined) is treated
137
137
  // as online — never block a request on an unavailable probe.
138
- const network = await getNetworkStatus();
138
+ const network = await getLastKnownNetworkStatus();
139
139
  if (network.isConnected === false) {
140
140
  Logger.warn(`Request blocked: device is offline. URL: ${config.url}`);
141
141
  return Promise.reject(new RequestOfflineError());
@@ -83,6 +83,24 @@ const expoSecureStoreBackend = {
83
83
  }
84
84
  };
85
85
  const resolveBackend = () => backend ?? expoSecureStoreBackend;
86
+
87
+ /**
88
+ * Every request reads the access token, and a secure-store read is a native
89
+ * keychain/keystore decrypt. Reads are therefore served from memory, written
90
+ * through by `storeToken` and dropped by `removeToken`. `generation` moves on
91
+ * every write, removal and backend swap, so a read that started before one of
92
+ * those cannot cache the value it saw.
93
+ */
94
+ const memo = new Map();
95
+ let generation = 0;
96
+ const forget = key => {
97
+ generation += 1;
98
+ if (key === undefined) {
99
+ memo.clear();
100
+ } else {
101
+ memo.delete(key);
102
+ }
103
+ };
86
104
  const requireBackend = op => {
87
105
  const b = resolveBackend();
88
106
  if (!b) {
@@ -100,6 +118,7 @@ const requireBackend = op => {
100
118
  */
101
119
  export const setKeychainImplementation = impl => {
102
120
  backend = impl;
121
+ forget();
103
122
  };
104
123
 
105
124
  /**
@@ -109,6 +128,7 @@ export const storeToken = async (key, token, expiry) => {
109
128
  // Never log the token or expiry values: Logger forwards to whatever remote
110
129
  // sink the app wired (Crashlytics/Sentry), so a token here leaves the device.
111
130
  Logger.info(`Storing token for key: ${key}`);
131
+ forget(key);
112
132
  try {
113
133
  const data = JSON.stringify({
114
134
  token,
@@ -117,8 +137,14 @@ export const storeToken = async (key, token, expiry) => {
117
137
  await requireBackend('store a token').setGenericPassword('token', data, {
118
138
  service: key
119
139
  });
140
+ forget(key);
141
+ memo.set(key, {
142
+ token,
143
+ expiry
144
+ });
120
145
  Logger.info(`Token stored successfully for key: ${key}`);
121
146
  } catch (error) {
147
+ forget(key);
122
148
  // Log AND rethrow. Swallowing this told the caller the token was persisted
123
149
  // when it was not, so an app would show a signed-in user holding a token
124
150
  // that never reached the keychain — the session then dies at the next
@@ -156,6 +182,10 @@ const parseTokenData = credentials => {
156
182
  * Retrieve token and check if expired.
157
183
  */
158
184
  export const getToken = async key => {
185
+ if (memo.has(key)) {
186
+ return memo.get(key) ?? null;
187
+ }
188
+ const seen = generation;
159
189
  try {
160
190
  const credentials = await requireBackend('read a token').getGenericPassword({
161
191
  service: key
@@ -164,24 +194,24 @@ export const getToken = async key => {
164
194
  // token, and every authenticated request, image and download asks for one.
165
195
  // Warning here put three info lines and a warning into the log, and into the
166
196
  // remote crash sink, on every one of those calls.
167
- if (!credentials) {
168
- return null;
197
+ let value = null;
198
+ if (credentials) {
199
+ const parsedData = parseTokenData(credentials);
200
+ if (parsedData) {
201
+ value = {
202
+ token: parsedData.token,
203
+ expiry: parsedData.expiry
204
+ };
205
+ } else {
206
+ // Something IS stored and it is not a token. That is corruption worth
207
+ // hearing about, and it is a different event from having no token.
208
+ Logger.warn(`Stored credentials for key "${key}" are not a valid token; ignoring them.`);
209
+ }
169
210
  }
170
- const parsedData = parseTokenData(credentials);
171
- if (!parsedData) {
172
- // Something IS stored and it is not a token. That is corruption worth
173
- // hearing about, and it is a different event from having no token.
174
- Logger.warn(`Stored credentials for key "${key}" are not a valid token; ignoring them.`);
175
- return null;
211
+ if (seen === generation) {
212
+ memo.set(key, value);
176
213
  }
177
- const {
178
- token,
179
- expiry
180
- } = parsedData;
181
- return {
182
- token,
183
- expiry
184
- };
214
+ return value;
185
215
  } catch (error) {
186
216
  const message = error instanceof Error ? error.message : String(error);
187
217
  // Both spellings matter. Android throws "Key permanently invalidated" when
@@ -204,6 +234,7 @@ export const getToken = async key => {
204
234
  * Remove token by key.
205
235
  */
206
236
  export const removeToken = async key => {
237
+ forget(key);
207
238
  try {
208
239
  await requireBackend('remove a token').resetGenericPassword({
209
240
  service: key
@@ -219,6 +250,8 @@ export const removeToken = async key => {
219
250
  return;
220
251
  }
221
252
  Logger.error(`Failed to remove token for key: ${key}`, error);
253
+ } finally {
254
+ forget(key);
222
255
  }
223
256
  };
224
257
  //# sourceMappingURL=authStore.js.map
@@ -5,10 +5,16 @@
5
5
  * Provides a flexible way to inject configuration without hard dependencies
6
6
  */
7
7
 
8
+ /**
9
+ * Info lines (each token refresh, each unauthorised URL) reach the device log
10
+ * and any remote sink, so a release build that sets nothing logs warnings and
11
+ * errors only.
12
+ */
13
+ export const DEFAULT_LOG_LEVEL = __DEV__ ? 'info' : 'warn';
8
14
  const DEFAULT_CONFIG = {
9
15
  baseUrl: null,
10
16
  loggingEnabled: true,
11
- logLevel: 'info',
17
+ logLevel: DEFAULT_LOG_LEVEL,
12
18
  environment: 'Development',
13
19
  country: 'in',
14
20
  appId: null,
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
 
3
+ import { numberFormat } from "./intlCache.js";
3
4
  const hasIntlNumber = () => typeof Intl !== 'undefined' && typeof Intl.NumberFormat === 'function';
4
5
  const indianGroupingFallback = (n, fractionDigits) => {
5
6
  // Indian numbering: 1,23,45,678.90 (last 3 digits, then groups of 2).
@@ -37,7 +38,7 @@ export const formatCurrency = (amount, options = {}) => {
37
38
  const minFrac = minimumFractionDigits ?? (Number.isInteger(amount) ? 0 : 2);
38
39
  if (hasIntlNumber()) {
39
40
  try {
40
- const formatted = new Intl.NumberFormat(locale, {
41
+ const formatted = numberFormat(locale, {
41
42
  style: 'currency',
42
43
  currency,
43
44
  minimumFractionDigits: minFrac,
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
 
3
+ import { dateTimeFormat, relativeTimeFormat } from "./intlCache.js";
3
4
  const toDate = value => {
4
5
  if (value instanceof Date) {
5
6
  return Number.isNaN(value.getTime()) ? null : value;
@@ -48,7 +49,7 @@ export const formatDate = (value, style = 'medium', locale = 'en-IN') => {
48
49
  }
49
50
  if (hasIntl()) {
50
51
  try {
51
- return new Intl.DateTimeFormat(locale, {
52
+ return dateTimeFormat(locale, {
52
53
  dateStyle: style
53
54
  }).format(d);
54
55
  } catch {
@@ -68,7 +69,7 @@ export const formatTime = (value, style = 'short', locale = 'en-IN') => {
68
69
  }
69
70
  if (hasIntl()) {
70
71
  try {
71
- return new Intl.DateTimeFormat(locale, {
72
+ return dateTimeFormat(locale, {
72
73
  timeStyle: style
73
74
  }).format(d);
74
75
  } catch {
@@ -88,7 +89,7 @@ export const formatDateTime = (value, dateStyle = 'medium', timeStyle = 'short',
88
89
  }
89
90
  if (hasIntl()) {
90
91
  try {
91
- return new Intl.DateTimeFormat(locale, {
92
+ return dateTimeFormat(locale, {
92
93
  dateStyle,
93
94
  timeStyle
94
95
  }).format(d);
@@ -145,9 +146,7 @@ export const formatRelativeTime = (value, base = new Date(), locale = 'en-IN') =
145
146
  const Rtf = hasIntl() ? Intl.RelativeTimeFormat : undefined;
146
147
  if (typeof Rtf === 'function') {
147
148
  try {
148
- return new Rtf(locale, {
149
- numeric: 'auto'
150
- }).format(amount, unit);
149
+ return relativeTimeFormat(Rtf, locale).format(amount, unit);
151
150
  } catch {
152
151
  /* fall through */
153
152
  }
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
 
3
+ import { dateTimeFormat } from "./intlCache.js";
3
4
  import { WINDOWS_TO_IANA } from "./windowsToIanaTable.js";
4
5
  const toDate = value => {
5
6
  if (value instanceof Date) {
@@ -263,7 +264,7 @@ export const formatTimeInZone = (value, ianaTimeZone) => {
263
264
  // answer available, not a genuine timezone conversion.
264
265
  return formatTime12h(d);
265
266
  }
266
- const parts = new Intl.DateTimeFormat('en-US', {
267
+ const parts = dateTimeFormat('en-US', {
267
268
  timeZone: ianaTimeZone,
268
269
  hour: 'numeric',
269
270
  minute: '2-digit',
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Building an Intl formatter costs far more than using one (and on Hermes for
5
+ * Android it goes through ICU over JNI), while list rows and slider frames call
6
+ * the formatters many times with the same arguments. Formatters are therefore
7
+ * built once per locale and options. The limit only guards against unbounded
8
+ * input such as arbitrary time zones; real apps use a handful of keys.
9
+ */
10
+ const LIMIT = 100;
11
+ const cached = (cache, key, create) => {
12
+ let formatter = cache.get(key);
13
+ if (formatter === undefined) {
14
+ formatter = create();
15
+ if (cache.size >= LIMIT) {
16
+ cache.clear();
17
+ }
18
+ cache.set(key, formatter);
19
+ }
20
+ return formatter;
21
+ };
22
+ const numberFormats = new Map();
23
+ const dateTimeFormats = new Map();
24
+ const relativeTimeFormats = new Map();
25
+ export const numberFormat = (locale, options = {}) => cached(numberFormats, `${locale}|${JSON.stringify(options)}`, () => new Intl.NumberFormat(locale, options));
26
+ export const dateTimeFormat = (locale, options) => cached(dateTimeFormats, `${locale}|${JSON.stringify(options)}`, () => new Intl.DateTimeFormat(locale, options));
27
+ export const relativeTimeFormat = (RelativeTimeFormat, locale) => cached(relativeTimeFormats, locale, () => new RelativeTimeFormat(locale, {
28
+ numeric: 'auto'
29
+ }));
30
+ //# sourceMappingURL=intlCache.js.map
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
 
3
+ import { numberFormat } from "./intlCache.js";
3
4
  const hasIntlNumber = () => typeof Intl !== 'undefined' && typeof Intl.NumberFormat === 'function';
4
5
 
5
6
  /**
@@ -16,7 +17,7 @@ export const formatNumber = (value, fractionDigits, locale = 'en-IN') => {
16
17
  opts.minimumFractionDigits = fractionDigits;
17
18
  opts.maximumFractionDigits = fractionDigits;
18
19
  }
19
- return new Intl.NumberFormat(locale, opts).format(value);
20
+ return numberFormat(locale, opts).format(value);
20
21
  } catch {
21
22
  /* fall through */
22
23
  }
@@ -35,7 +36,7 @@ export const formatPercent = (value, fractionDigits = 0, asRatio = true, locale
35
36
  const ratio = asRatio ? value : value / 100;
36
37
  if (hasIntlNumber()) {
37
38
  try {
38
- return new Intl.NumberFormat(locale, {
39
+ return numberFormat(locale, {
39
40
  style: 'percent',
40
41
  minimumFractionDigits: fractionDigits,
41
42
  maximumFractionDigits: fractionDigits
@@ -57,7 +58,7 @@ export const formatCompactNumber = (value, locale = 'en-IN') => {
57
58
  }
58
59
  if (hasIntlNumber()) {
59
60
  try {
60
- return new Intl.NumberFormat(locale, {
61
+ return numberFormat(locale, {
61
62
  notation: 'compact',
62
63
  maximumFractionDigits: 1
63
64
  }).format(value);
@@ -48,6 +48,8 @@ export { addNetworkStatusListener, getNetworkStatus, setNetworkStatusImplementat
48
48
  // Storage: the API and the seam. No native package is required; wire one with
49
49
  // setStorageImplementation to get persistence.
50
50
  export { isStorageAvailable, Storage, setStorageImplementation } from "./storage/index.js";
51
+ // Bytes
52
+ export { encodeBase64 } from "./utils/base64.js";
51
53
  // Validators
52
54
  export { isAadhaar, isAlphanumeric, isBetween, isEmail, isGstin, isHexColor, isIfsc, isIndianMobile, isIndianPincode, isInteger, isNonEmpty, isNumeric, isPan, isPhone, isStrongPassword, isUrl, maxLength, minLength } from "./validators/index.js";
53
55
  // Version check (the ForceUpdateDialog UI lives in @webority/mobile-ui)
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import { setKeychainImplementation } from "./auth/authStore.js";
9
- import { setConfig } from "./config/index.js";
9
+ import { DEFAULT_LOG_LEVEL, setConfig } from "./config/index.js";
10
10
  import { Logger } from "./logger/index.js";
11
11
  import { setNetworkStatusImplementation } from "./network/networkStatus.js";
12
12
  import { setStorageImplementation } from "./storage/index.js";
@@ -35,7 +35,7 @@ export const initWebority = config => {
35
35
  setConfig({
36
36
  baseUrl: config.baseUrl,
37
37
  loggingEnabled: config.loggingEnabled ?? true,
38
- logLevel: config.logLevel || 'info',
38
+ logLevel: config.logLevel || DEFAULT_LOG_LEVEL,
39
39
  environment: config.environment || 'Development',
40
40
  country: (config.country || 'in').toLowerCase(),
41
41
  appId: config.appId ?? null,
@@ -18,6 +18,13 @@ const DEFAULT_STATUS = {
18
18
  * One backend also means one behaviour, rather than each app choosing.
19
19
  */
20
20
  let override = null;
21
+ let lastKnown = null;
22
+ let stopFollowing = null;
23
+ const stopFollowingNetwork = () => {
24
+ stopFollowing?.();
25
+ stopFollowing = null;
26
+ lastKnown = null;
27
+ };
21
28
 
22
29
  /**
23
30
  * Replace the backend. Intended for TESTS and for the showcase, which uses it
@@ -26,6 +33,7 @@ let override = null;
26
33
  */
27
34
  export const setNetworkStatusImplementation = impl => {
28
35
  override = impl;
36
+ stopFollowingNetwork();
29
37
  };
30
38
  const resolveNetInfo = () => override ?? expoNetworkBackend;
31
39
 
@@ -96,6 +104,35 @@ export const addNetworkStatusListener = listener => {
96
104
  return ni.addEventListener(state => listener(normalize(state)));
97
105
  };
98
106
 
107
+ /**
108
+ * The connectivity the HTTP client checks before every request. One listener,
109
+ * attached on first use, keeps it current, so a request costs no native probe:
110
+ * one probe seeds the value and the listener's events replace it.
111
+ */
112
+ export const getLastKnownNetworkStatus = async () => {
113
+ if (!stopFollowing) {
114
+ try {
115
+ stopFollowing = addNetworkStatusListener(status => {
116
+ lastKnown = status;
117
+ });
118
+ } catch (error) {
119
+ Logger.error('[network] could not follow network changes', error);
120
+ return getNetworkStatus();
121
+ }
122
+ }
123
+ if (lastKnown) {
124
+ return lastKnown;
125
+ }
126
+ const following = stopFollowing;
127
+ const probed = await getNetworkStatus();
128
+ if (stopFollowing !== following) {
129
+ return probed;
130
+ }
131
+ // An event that arrived during the probe is newer than the probe.
132
+ lastKnown ??= probed;
133
+ return lastKnown;
134
+ };
135
+
99
136
  /**
100
137
  * React hook that returns the current network status and re-renders on change.
101
138
  * Reports an optimistic online default when NetInfo is not installed.
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+
3
+ // Small enough for every engine's argument limit in `String.fromCharCode.apply`.
4
+ const CHUNK = 0x2000;
5
+
6
+ /**
7
+ * Base64 of raw bytes, for a `data:` URI. Builds a Latin-1 string a chunk at a
8
+ * time and hands it to the engine's native `btoa` (Hermes ships it), which is
9
+ * many times faster on the JS thread than encoding byte by byte in JS.
10
+ */
11
+ export const encodeBase64 = buffer => {
12
+ const bytes = new Uint8Array(buffer);
13
+ let binary = '';
14
+ for (let i = 0; i < bytes.length; i += CHUNK) {
15
+ binary += String.fromCharCode.apply(null, bytes.subarray(i, i + CHUNK));
16
+ }
17
+ return btoa(binary);
18
+ };
19
+ //# sourceMappingURL=base64.js.map
@@ -34,6 +34,12 @@ export interface Config {
34
34
  */
35
35
  logCurl: boolean;
36
36
  }
37
+ /**
38
+ * Info lines (each token refresh, each unauthorised URL) reach the device log
39
+ * and any remote sink, so a release build that sets nothing logs warnings and
40
+ * errors only.
41
+ */
42
+ export declare const DEFAULT_LOG_LEVEL: LogLevel;
37
43
  /**
38
44
  * Set global configuration for all Webority packages
39
45
  */
@@ -0,0 +1,6 @@
1
+ export declare const numberFormat: (locale: string, options?: Intl.NumberFormatOptions) => Intl.NumberFormat;
2
+ export declare const dateTimeFormat: (locale: string, options: Intl.DateTimeFormatOptions) => Intl.DateTimeFormat;
3
+ export declare const relativeTimeFormat: (RelativeTimeFormat: new (locale: string, options?: {
4
+ numeric?: "always" | "auto";
5
+ }) => Intl.RelativeTimeFormat, locale: string) => Intl.RelativeTimeFormat;
6
+ //# sourceMappingURL=intlCache.d.ts.map
@@ -48,6 +48,7 @@ export type { NetworkConnectionType, NetworkStatus, NetworkStatusLike, NetworkSt
48
48
  export { addNetworkStatusListener, getNetworkStatus, setNetworkStatusImplementation, useNetworkStatus } from './network';
49
49
  export type { AsyncStorageLike, StorageApi } from './storage';
50
50
  export { isStorageAvailable, Storage, setStorageImplementation } from './storage';
51
+ export { encodeBase64 } from './utils/base64';
51
52
  export type { PasswordRules } from './validators';
52
53
  export { isAadhaar, isAlphanumeric, isBetween, isEmail, isGstin, isHexColor, isIfsc, isIndianMobile, isIndianPincode, isInteger, isNonEmpty, isNumeric, isPan, isPhone, isStrongPassword, isUrl, maxLength, minLength } from './validators';
53
54
  export type { DeviceInfoLike, ParsedVersion, Platform, UpdateInfo, UpdateType, VersionCheckResult, VersionCheckType, VersionComparison, VersionData } from './versionCheck';
@@ -11,7 +11,7 @@ export interface InitConfig {
11
11
  baseUrl: string;
12
12
  /** Enable/disable logging. Defaults to `true`. */
13
13
  loggingEnabled?: boolean;
14
- /** Log level. Defaults to `'info'`. */
14
+ /** Log level. Defaults to `'info'` in development and `'warn'` in a release build. */
15
15
  logLevel?: LogLevel;
16
16
  /** Environment. Defaults to `'Development'`. */
17
17
  environment?: Environment;
@@ -34,6 +34,12 @@ export declare const getNetworkStatus: () => Promise<NetworkStatus>;
34
34
  * Subscribe to network status changes. Returns an unsubscribe function.
35
35
  */
36
36
  export declare const addNetworkStatusListener: (listener: NetworkStatusListener) => (() => void);
37
+ /**
38
+ * The connectivity the HTTP client checks before every request. One listener,
39
+ * attached on first use, keeps it current, so a request costs no native probe:
40
+ * one probe seeds the value and the listener's events replace it.
41
+ */
42
+ export declare const getLastKnownNetworkStatus: () => Promise<NetworkStatus>;
37
43
  /**
38
44
  * React hook that returns the current network status and re-renders on change.
39
45
  * Reports an optimistic online default when NetInfo is not installed.
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Base64 of raw bytes, for a `data:` URI. Builds a Latin-1 string a chunk at a
3
+ * time and hands it to the engine's native `btoa` (Hermes ships it), which is
4
+ * many times faster on the JS thread than encoding byte by byte in JS.
5
+ */
6
+ export declare const encodeBase64: (buffer: ArrayBuffer) => string;
7
+ //# sourceMappingURL=base64.d.ts.map
@@ -34,6 +34,12 @@ export interface Config {
34
34
  */
35
35
  logCurl: boolean;
36
36
  }
37
+ /**
38
+ * Info lines (each token refresh, each unauthorised URL) reach the device log
39
+ * and any remote sink, so a release build that sets nothing logs warnings and
40
+ * errors only.
41
+ */
42
+ export declare const DEFAULT_LOG_LEVEL: LogLevel;
37
43
  /**
38
44
  * Set global configuration for all Webority packages
39
45
  */
@@ -0,0 +1,6 @@
1
+ export declare const numberFormat: (locale: string, options?: Intl.NumberFormatOptions) => Intl.NumberFormat;
2
+ export declare const dateTimeFormat: (locale: string, options: Intl.DateTimeFormatOptions) => Intl.DateTimeFormat;
3
+ export declare const relativeTimeFormat: (RelativeTimeFormat: new (locale: string, options?: {
4
+ numeric?: "always" | "auto";
5
+ }) => Intl.RelativeTimeFormat, locale: string) => Intl.RelativeTimeFormat;
6
+ //# sourceMappingURL=intlCache.d.ts.map
@@ -48,6 +48,7 @@ export type { NetworkConnectionType, NetworkStatus, NetworkStatusLike, NetworkSt
48
48
  export { addNetworkStatusListener, getNetworkStatus, setNetworkStatusImplementation, useNetworkStatus } from './network/index.js';
49
49
  export type { AsyncStorageLike, StorageApi } from './storage/index.js';
50
50
  export { isStorageAvailable, Storage, setStorageImplementation } from './storage/index.js';
51
+ export { encodeBase64 } from './utils/base64.js';
51
52
  export type { PasswordRules } from './validators/index.js';
52
53
  export { isAadhaar, isAlphanumeric, isBetween, isEmail, isGstin, isHexColor, isIfsc, isIndianMobile, isIndianPincode, isInteger, isNonEmpty, isNumeric, isPan, isPhone, isStrongPassword, isUrl, maxLength, minLength } from './validators/index.js';
53
54
  export type { DeviceInfoLike, ParsedVersion, Platform, UpdateInfo, UpdateType, VersionCheckResult, VersionCheckType, VersionComparison, VersionData } from './versionCheck/index.js';
@@ -11,7 +11,7 @@ export interface InitConfig {
11
11
  baseUrl: string;
12
12
  /** Enable/disable logging. Defaults to `true`. */
13
13
  loggingEnabled?: boolean;
14
- /** Log level. Defaults to `'info'`. */
14
+ /** Log level. Defaults to `'info'` in development and `'warn'` in a release build. */
15
15
  logLevel?: LogLevel;
16
16
  /** Environment. Defaults to `'Development'`. */
17
17
  environment?: Environment;
@@ -34,6 +34,12 @@ export declare const getNetworkStatus: () => Promise<NetworkStatus>;
34
34
  * Subscribe to network status changes. Returns an unsubscribe function.
35
35
  */
36
36
  export declare const addNetworkStatusListener: (listener: NetworkStatusListener) => (() => void);
37
+ /**
38
+ * The connectivity the HTTP client checks before every request. One listener,
39
+ * attached on first use, keeps it current, so a request costs no native probe:
40
+ * one probe seeds the value and the listener's events replace it.
41
+ */
42
+ export declare const getLastKnownNetworkStatus: () => Promise<NetworkStatus>;
37
43
  /**
38
44
  * React hook that returns the current network status and re-renders on change.
39
45
  * Reports an optimistic online default when NetInfo is not installed.
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Base64 of raw bytes, for a `data:` URI. Builds a Latin-1 string a chunk at a
3
+ * time and hands it to the engine's native `btoa` (Hermes ships it), which is
4
+ * many times faster on the JS thread than encoding byte by byte in JS.
5
+ */
6
+ export declare const encodeBase64: (buffer: ArrayBuffer) => string;
7
+ //# sourceMappingURL=base64.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webority/mobile-core",
3
- "version": "0.0.26",
3
+ "version": "0.0.27",
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",