@webority-technologies/mobile-core 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/README.md +15 -0
  2. package/lib/commonjs/api/publicClient.js +80 -0
  3. package/lib/commonjs/api/userClient.js +368 -0
  4. package/lib/commonjs/auth/authStore.js +155 -0
  5. package/lib/commonjs/auth/index.js +44 -0
  6. package/lib/commonjs/auth/jwt.js +88 -0
  7. package/lib/commonjs/config/index.js +65 -0
  8. package/lib/commonjs/formatters/currency.js +69 -0
  9. package/lib/commonjs/formatters/date.js +168 -0
  10. package/lib/commonjs/formatters/index.js +77 -0
  11. package/lib/commonjs/formatters/initials.js +32 -0
  12. package/lib/commonjs/formatters/number.js +88 -0
  13. package/lib/commonjs/formatters/phone.js +67 -0
  14. package/lib/commonjs/index.js +383 -0
  15. package/lib/commonjs/initSDK.js +45 -0
  16. package/lib/commonjs/initUserAuth.js +38 -0
  17. package/lib/commonjs/logger/index.js +137 -0
  18. package/lib/commonjs/network/index.js +25 -0
  19. package/lib/commonjs/network/networkStatus.js +98 -0
  20. package/lib/commonjs/package.json +1 -0
  21. package/lib/commonjs/permissions/index.js +200 -0
  22. package/lib/commonjs/storage/index.js +193 -0
  23. package/lib/commonjs/utils/imageCompression.js +75 -0
  24. package/lib/commonjs/utils/index.js +13 -0
  25. package/lib/commonjs/validators/index.js +193 -0
  26. package/lib/commonjs/versionCheck/index.js +395 -0
  27. package/lib/module/api/publicClient.js +76 -0
  28. package/lib/module/api/userClient.js +360 -0
  29. package/lib/module/auth/authStore.js +148 -0
  30. package/lib/module/auth/index.js +5 -0
  31. package/lib/module/auth/jwt.js +82 -0
  32. package/lib/module/config/index.js +57 -0
  33. package/lib/module/formatters/currency.js +64 -0
  34. package/lib/module/formatters/date.js +160 -0
  35. package/lib/module/formatters/index.js +8 -0
  36. package/lib/module/formatters/initials.js +27 -0
  37. package/lib/module/formatters/number.js +81 -0
  38. package/lib/module/formatters/phone.js +61 -0
  39. package/lib/module/index.js +30 -0
  40. package/lib/module/initSDK.js +40 -0
  41. package/lib/module/initUserAuth.js +34 -0
  42. package/lib/module/logger/index.js +133 -0
  43. package/lib/module/network/index.js +4 -0
  44. package/lib/module/network/networkStatus.js +91 -0
  45. package/lib/module/package.json +1 -0
  46. package/lib/module/permissions/index.js +197 -0
  47. package/lib/module/storage/index.js +189 -0
  48. package/lib/module/utils/imageCompression.js +69 -0
  49. package/lib/module/utils/index.js +4 -0
  50. package/lib/module/validators/index.js +171 -0
  51. package/lib/module/versionCheck/index.js +390 -0
  52. package/lib/typescript/commonjs/api/publicClient.d.ts +11 -0
  53. package/lib/typescript/commonjs/api/userClient.d.ts +49 -0
  54. package/lib/typescript/commonjs/auth/authStore.d.ts +43 -0
  55. package/lib/typescript/commonjs/auth/index.d.ts +5 -0
  56. package/lib/typescript/commonjs/auth/jwt.d.ts +31 -0
  57. package/lib/typescript/commonjs/config/index.d.ts +49 -0
  58. package/lib/typescript/commonjs/formatters/currency.d.ts +24 -0
  59. package/lib/typescript/commonjs/formatters/date.d.ts +22 -0
  60. package/lib/typescript/commonjs/formatters/index.d.ts +8 -0
  61. package/lib/typescript/commonjs/formatters/initials.d.ts +11 -0
  62. package/lib/typescript/commonjs/formatters/number.d.ts +15 -0
  63. package/lib/typescript/commonjs/formatters/phone.d.ts +13 -0
  64. package/lib/typescript/commonjs/index.d.ts +30 -0
  65. package/lib/typescript/commonjs/initSDK.d.ts +40 -0
  66. package/lib/typescript/commonjs/initUserAuth.d.ts +22 -0
  67. package/lib/typescript/commonjs/logger/index.d.ts +42 -0
  68. package/lib/typescript/commonjs/network/index.d.ts +3 -0
  69. package/lib/typescript/commonjs/network/networkStatus.d.ts +27 -0
  70. package/lib/typescript/commonjs/package.json +1 -0
  71. package/lib/typescript/commonjs/permissions/index.d.ts +20 -0
  72. package/lib/typescript/commonjs/storage/index.d.ts +42 -0
  73. package/lib/typescript/commonjs/utils/imageCompression.d.ts +31 -0
  74. package/lib/typescript/commonjs/utils/index.d.ts +3 -0
  75. package/lib/typescript/commonjs/validators/index.d.ts +46 -0
  76. package/lib/typescript/commonjs/versionCheck/index.d.ts +80 -0
  77. package/lib/typescript/module/api/publicClient.d.ts +11 -0
  78. package/lib/typescript/module/api/userClient.d.ts +49 -0
  79. package/lib/typescript/module/auth/authStore.d.ts +43 -0
  80. package/lib/typescript/module/auth/index.d.ts +5 -0
  81. package/lib/typescript/module/auth/jwt.d.ts +31 -0
  82. package/lib/typescript/module/config/index.d.ts +49 -0
  83. package/lib/typescript/module/formatters/currency.d.ts +24 -0
  84. package/lib/typescript/module/formatters/date.d.ts +22 -0
  85. package/lib/typescript/module/formatters/index.d.ts +8 -0
  86. package/lib/typescript/module/formatters/initials.d.ts +11 -0
  87. package/lib/typescript/module/formatters/number.d.ts +15 -0
  88. package/lib/typescript/module/formatters/phone.d.ts +13 -0
  89. package/lib/typescript/module/index.d.ts +30 -0
  90. package/lib/typescript/module/initSDK.d.ts +40 -0
  91. package/lib/typescript/module/initUserAuth.d.ts +22 -0
  92. package/lib/typescript/module/logger/index.d.ts +42 -0
  93. package/lib/typescript/module/network/index.d.ts +3 -0
  94. package/lib/typescript/module/network/networkStatus.d.ts +27 -0
  95. package/lib/typescript/module/package.json +1 -0
  96. package/lib/typescript/module/permissions/index.d.ts +20 -0
  97. package/lib/typescript/module/storage/index.d.ts +42 -0
  98. package/lib/typescript/module/utils/imageCompression.d.ts +31 -0
  99. package/lib/typescript/module/utils/index.d.ts +3 -0
  100. package/lib/typescript/module/validators/index.d.ts +46 -0
  101. package/lib/typescript/module/versionCheck/index.d.ts +80 -0
  102. package/package.json +112 -0
package/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # @webority-technologies/mobile-core
2
+
3
+ The platform layer for Webority React Native apps: HTTP clients, auth and token storage, config,
4
+ logging, formatters, validators, network status, permissions, storage, image compression and
5
+ version checks. **No UI** — components live in
6
+ [`@webority-technologies/mobile-ui`](https://www.npmjs.com/package/@webority-technologies/mobile-ui).
7
+
8
+ ```bash
9
+ npm install @webority-technologies/mobile-core
10
+ ```
11
+
12
+ External runtime dependencies: `axios` and `axios-retry`. Every native peer is optional and lazily
13
+ required, so the package installs and runs without any of them.
14
+
15
+ Full documentation lives in the repository's `docs/` directory.
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _axios = _interopRequireDefault(require("axios"));
8
+ var _axiosRetry = _interopRequireDefault(require("axios-retry"));
9
+ var _index = require("../config/index.js");
10
+ var _index2 = require("../logger/index.js");
11
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
12
+ // Define timeout for requests
13
+ const REQUEST_TIMEOUT = 30000; // 30 seconds
14
+
15
+ /**
16
+ * Public Axios Client
17
+ * - Base URL: Set your public API base URL.
18
+ * - No Authorization header is attached.
19
+ * - Includes timeout configuration and error handling.
20
+ * - Handles specific HTTP status codes.
21
+ */
22
+ const publicClient = _axios.default.create({
23
+ baseURL: (0, _index.getConfigValue)('baseUrl') ?? undefined,
24
+ headers: {
25
+ 'Content-Type': 'application/json',
26
+ Accept: 'application/json',
27
+ 'Accept-Encoding': 'gzip, deflate, br'
28
+ },
29
+ timeout: REQUEST_TIMEOUT
30
+ });
31
+
32
+ // Configure axios-retry with modified exponential backoff
33
+ const retryConfig = {
34
+ retries: 3,
35
+ retryDelay: retryCount => retryCount === 0 ? 0 : Math.pow(2, retryCount) * 1000,
36
+ // Immediate retry for the first attempt
37
+ retryCondition: error => {
38
+ // Status-based ONLY, deliberately: a bare network error must never be
39
+ // retried here. This client carries login and OTP, which are not
40
+ // idempotent — the request may well have reached the server before the
41
+ // connection broke, so a retry can create a second session the user never
42
+ // sees. axios-retry's isNetworkOrIdempotentRequestError does NOT protect
43
+ // against this: its isNetworkError branch never looks at the HTTP method,
44
+ // so it retried POSTs too.
45
+ const status = error.response?.status;
46
+ return typeof status === 'number' && (status === 429 || status >= 500 && status <= 599);
47
+ }
48
+ };
49
+ (0, _axiosRetry.default)(publicClient, retryConfig);
50
+
51
+ // Add request interceptor for logging
52
+ publicClient.interceptors.request.use(config => {
53
+ _index2.Logger.info(`Request: ${config.method?.toUpperCase()} ${config.url}`);
54
+ return config;
55
+ }, error => {
56
+ const message = error instanceof Error ? error.message : String(error);
57
+ _index2.Logger.error('Request error:', message);
58
+ return Promise.reject(error);
59
+ });
60
+
61
+ // Add response interceptor for error handling
62
+ publicClient.interceptors.response.use(response => {
63
+ _index2.Logger.info(`Response: ${response.status} from ${response.config.url}`);
64
+ return response;
65
+ }, error => {
66
+ if (_axios.default.isAxiosError(error)) {
67
+ const originalRequest = error.config;
68
+ if (!error.response) {
69
+ _index2.Logger.error(`Network error or no response from server: ${error.message}, URL: ${originalRequest?.url}`);
70
+ } else {
71
+ _index2.Logger.error(`Response error: ${error.response.data ?? error.message}, URL: ${originalRequest?.url}, Status: ${error.response.status}`);
72
+ }
73
+ } else {
74
+ const message = error instanceof Error ? error.message : String(error);
75
+ _index2.Logger.error(`Unknown response error: ${message}`);
76
+ }
77
+ return Promise.reject(error);
78
+ });
79
+ var _default = exports.default = publicClient;
80
+ //# sourceMappingURL=publicClient.js.map
@@ -0,0 +1,368 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.injectStore = exports.injectAuthActions = exports.default = exports.SESSION_TERMINATION_CODES = exports.SESSION_INVALID_HEADER = exports.RequestOfflineError = void 0;
7
+ var _axios = _interopRequireWildcard(require("axios"));
8
+ var _axiosRetry = _interopRequireDefault(require("axios-retry"));
9
+ var _authStore = require("../auth/authStore.js");
10
+ var _index = require("../config/index.js");
11
+ var _index2 = require("../logger/index.js");
12
+ var _networkStatus = require("../network/networkStatus.js");
13
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
14
+ 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); }
15
+ // Define timeout for requests
16
+ const REQUEST_TIMEOUT = 30000; // 30 seconds
17
+
18
+ /**
19
+ * Thrown by the request interceptor when the device is offline, before the
20
+ * request ever reaches the adapter (and therefore the network). Distinct from
21
+ * an AxiosError so callers can show an offline message instead of a generic
22
+ * request failure.
23
+ */
24
+ class RequestOfflineError extends Error {
25
+ constructor(message = 'Request blocked: device is offline') {
26
+ super(message);
27
+ this.name = 'RequestOfflineError';
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Sub-codes a 401 response body carries when the session itself is dead
33
+ * (terminated by an admin, or expired server-side) rather than merely
34
+ * out-of-date. These never warrant a refresh attempt — only logout.
35
+ */
36
+ exports.RequestOfflineError = RequestOfflineError;
37
+ const SESSION_TERMINATION_CODES = exports.SESSION_TERMINATION_CODES = {
38
+ TERMINATED: 'SESSION_TERMINATED',
39
+ EXPIRED: 'SESSION_EXPIRED'
40
+ };
41
+ const TERMINATION_CODES = new Set(Object.values(SESSION_TERMINATION_CODES));
42
+
43
+ /**
44
+ * The header is the AUTHORITATIVE signal, verified against the backend:
45
+ * `WeborityJwtMiddleware.ReturnUnauthorized` answers an invalidated session with
46
+ * `401` + `X-Security-Stamp-Invalid: true` and a body of
47
+ * `{ error: 'Unauthorized', message: '...' }` — no code field at all, and
48
+ * `error` is a string rather than an object. An earlier version of this checked
49
+ * only body fields and would therefore never have fired against a real server.
50
+ *
51
+ * The body codes below are kept as a secondary path for any head that does send
52
+ * an explicit sub-code; they match on exact strings, so looking is free.
53
+ */
54
+ const SESSION_INVALID_HEADER = exports.SESSION_INVALID_HEADER = 'x-security-stamp-invalid';
55
+ const hasSessionInvalidHeader = headers => {
56
+ if (!headers || typeof headers !== 'object') {
57
+ return false;
58
+ }
59
+ const get = headers.get;
60
+ const raw = typeof get === 'function' ? get.call(headers, SESSION_INVALID_HEADER) : headers[SESSION_INVALID_HEADER];
61
+ return String(raw).toLowerCase() === 'true';
62
+ };
63
+ const getSessionTerminationCode = data => {
64
+ if (!data || typeof data !== 'object') {
65
+ return undefined;
66
+ }
67
+ const body = data;
68
+ const nested = body.error;
69
+ const candidates = [body.code, body.errorCode, nested && typeof nested === 'object' ? nested.code : undefined];
70
+ for (const candidate of candidates) {
71
+ if (typeof candidate === 'string' && TERMINATION_CODES.has(candidate)) {
72
+ return candidate;
73
+ }
74
+ }
75
+ return undefined;
76
+ };
77
+
78
+ /**
79
+ * Minimal Redux-store shape required by the user client to dispatch
80
+ * refresh-token / logout actions.
81
+ */
82
+
83
+ /**
84
+ * Internal extension of axios request config to track retry attempts.
85
+ */
86
+
87
+ const userClient = _axios.default.create({
88
+ baseURL: (0, _index.getConfigValue)('baseUrl') ?? undefined,
89
+ headers: {
90
+ 'Content-Type': 'application/json',
91
+ 'Accept-Encoding': 'gzip, deflate, br'
92
+ },
93
+ timeout: REQUEST_TIMEOUT // Set timeout for all requests
94
+ });
95
+
96
+ // Configure axios-retry with modified exponential backoff
97
+ (0, _axiosRetry.default)(userClient, {
98
+ retries: 3,
99
+ retryDelay: retryCount => retryCount === 0 ? 0 : Math.pow(2, retryCount) * 1000,
100
+ // Immediate retry for the first attempt
101
+ retryCondition: error => {
102
+ // Status-based ONLY — see publicClient for the reasoning. A bare network
103
+ // error carries no response, so it is not retried: the write may already
104
+ // have landed server-side, and this client issues non-idempotent requests.
105
+ const status = error.response?.status;
106
+ return typeof status === 'number' && (status === 429 || status >= 500 && status <= 599);
107
+ }
108
+ });
109
+ let store;
110
+ let authActions = {
111
+ logout: () => undefined,
112
+ refreshToken: () => undefined
113
+ };
114
+ const injectStore = _store => {
115
+ store = _store;
116
+ };
117
+ exports.injectStore = injectStore;
118
+ const injectAuthActions = actions => {
119
+ authActions = {
120
+ ...authActions,
121
+ ...actions
122
+ };
123
+ };
124
+
125
+ // Single promise to track token refresh
126
+ exports.injectAuthActions = injectAuthActions;
127
+ let refreshTokenPromise = null;
128
+
129
+ // Queue for failed requests
130
+ let failedQueue = [];
131
+
132
+ /**
133
+ * Processes the queue of failed requests.
134
+ */
135
+ const processQueue = (error, token = null) => {
136
+ failedQueue.forEach(({
137
+ resolve,
138
+ reject
139
+ }) => {
140
+ if (error) {
141
+ reject(error);
142
+ } else if (token !== null) {
143
+ resolve(token);
144
+ }
145
+ });
146
+ failedQueue = [];
147
+ };
148
+ const getErrorMessage = error => {
149
+ if (error instanceof Error) {
150
+ return error.message;
151
+ }
152
+ if (typeof error === 'string') {
153
+ return error;
154
+ }
155
+ return 'Unknown error';
156
+ };
157
+ const setAuthHeader = (config, token) => {
158
+ if (config.headers instanceof _axios.AxiosHeaders) {
159
+ config.headers.set('Authorization', `Bearer ${token}`);
160
+ } else {
161
+ // Fallback in case headers were provided as a plain object.
162
+ config.headers = _axios.AxiosHeaders.from({
163
+ ...config.headers,
164
+ Authorization: `Bearer ${token}`
165
+ });
166
+ }
167
+ };
168
+ const requireStore = () => {
169
+ if (!store) {
170
+ throw new Error('User package store is not initialized. Call initUserAuth first.');
171
+ }
172
+ return store;
173
+ };
174
+
175
+ /**
176
+ * Attach Authorization Token Before Requests
177
+ */
178
+ userClient.interceptors.request.use(async config => {
179
+ // Reject before any network call when the device is known to be offline.
180
+ // Unknown connectivity (netinfo not installed, or undetermined) is treated
181
+ // as online — never block a request on an unavailable probe.
182
+ const network = await (0, _networkStatus.getNetworkStatus)();
183
+ if (network.isConnected === false) {
184
+ _index2.Logger.warn(`Request blocked: device is offline. URL: ${config.url}`);
185
+ return Promise.reject(new RequestOfflineError());
186
+ }
187
+ try {
188
+ // Skip token check for refresh token endpoint to prevent infinite loop
189
+ if (config.url?.includes('/Account/RefreshToken')) {
190
+ return config;
191
+ }
192
+ const token = await (0, _authStore.getToken)('accessToken');
193
+
194
+ // Check if token exists
195
+ if (!token?.token) {
196
+ throw new Error('No access token available');
197
+ }
198
+
199
+ // Check if token is expired
200
+ const currentTime = Date.now();
201
+ const tokenExpiry = Date.parse(token.expiry);
202
+ const bufferTime = 30 * 1000; // 30 seconds buffer
203
+
204
+ // If token is expired or about to expire, try to refresh it first
205
+ if (tokenExpiry <= currentTime + bufferTime) {
206
+ _index2.Logger.info('Access token expired or about to expire, refreshing before request');
207
+
208
+ // If we already have a refresh in progress, wait for it
209
+ if (refreshTokenPromise) {
210
+ _index2.Logger.info('Token refresh already in progress, waiting for completion');
211
+ try {
212
+ const newToken = await refreshTokenPromise;
213
+ setAuthHeader(config, newToken);
214
+ return config;
215
+ } catch (err) {
216
+ _index2.Logger.error('Failed to use refreshed token:', getErrorMessage(err));
217
+ throw err;
218
+ }
219
+ }
220
+
221
+ // Start a new token refresh process
222
+ refreshTokenPromise = (async () => {
223
+ try {
224
+ const refreshToken = await (0, _authStore.getToken)('refreshToken');
225
+ if (!refreshToken?.token) {
226
+ _index2.Logger.error('No refresh token available');
227
+ throw new Error('No refresh token available');
228
+ }
229
+
230
+ // Check if refresh token is expired
231
+ const refreshTokenExpiry = Date.parse(refreshToken.expiry);
232
+ if (refreshTokenExpiry <= currentTime) {
233
+ _index2.Logger.error('Refresh token expired');
234
+ throw new Error('Refresh token expired');
235
+ }
236
+ _index2.Logger.info('Dispatching refresh token action');
237
+ const activeStore = requireStore();
238
+ const refreshResult = await activeStore.dispatch(authActions.refreshToken());
239
+ if (refreshResult.error) {
240
+ throw new Error(`Token refresh failed: ${refreshResult.error.message || 'Unknown error'}`);
241
+ }
242
+ const newAccessToken = await (0, _authStore.getToken)('accessToken');
243
+ if (!newAccessToken?.token) {
244
+ throw new Error('Failed to retrieve new access token after refresh');
245
+ }
246
+ _index2.Logger.info('Token refreshed successfully before request');
247
+ return newAccessToken.token;
248
+ } catch (error) {
249
+ _index2.Logger.error('Token refresh failed before request:', getErrorMessage(error));
250
+ await requireStore().dispatch(authActions.logout());
251
+ throw error;
252
+ } finally {
253
+ refreshTokenPromise = null;
254
+ }
255
+ })();
256
+ try {
257
+ // Wait for the refresh to complete
258
+ const newToken = await refreshTokenPromise;
259
+ setAuthHeader(config, newToken);
260
+ return config;
261
+ } catch (err) {
262
+ return Promise.reject(err);
263
+ }
264
+ }
265
+
266
+ // Token is valid, proceed with request
267
+ setAuthHeader(config, token.token);
268
+ return config;
269
+ } catch (error) {
270
+ _index2.Logger.error('Failed to attach token:', getErrorMessage(error));
271
+ return Promise.reject(error);
272
+ }
273
+ }, error => {
274
+ _index2.Logger.error('Request error:', getErrorMessage(error));
275
+ return Promise.reject(error);
276
+ });
277
+
278
+ /**
279
+ * Handle Response & Token Refresh
280
+ */
281
+ userClient.interceptors.response.use(response => response, async error => {
282
+ if (!_axios.default.isAxiosError(error)) {
283
+ _index2.Logger.error(`Non-axios error in response interceptor: ${getErrorMessage(error)}`);
284
+ return Promise.reject(error);
285
+ }
286
+ const axiosError = error;
287
+ const originalRequest = axiosError.config;
288
+ if (!axiosError.response) {
289
+ _index2.Logger.error(`Network error or no response from server: ${axiosError.message}, URL: ${originalRequest?.url}`);
290
+ return Promise.reject(error);
291
+ }
292
+ if (axiosError.response.status === 401 && originalRequest && !originalRequest._retry) {
293
+ const terminationCode = getSessionTerminationCode(axiosError.response.data) ?? (hasSessionInvalidHeader(axiosError.response.headers) ? SESSION_TERMINATION_CODES.TERMINATED : undefined);
294
+ if (terminationCode) {
295
+ // The session is dead server-side — a refresh would only burn a
296
+ // pointless round-trip, so go straight to logout.
297
+ _index2.Logger.warn(`Session ${terminationCode}, logging out without refresh. URL: ${originalRequest.url}`);
298
+ await requireStore().dispatch(authActions.logout());
299
+ return Promise.reject(error);
300
+ }
301
+ _index2.Logger.warn(`Unauthorized URL: ${originalRequest.url}`);
302
+ originalRequest._retry = true;
303
+
304
+ // If we already have a refresh in progress, queue this request
305
+ if (refreshTokenPromise) {
306
+ return new Promise((resolve, reject) => {
307
+ failedQueue.push({
308
+ resolve,
309
+ reject
310
+ });
311
+ }).then(token => {
312
+ _index2.Logger.info('Retrying request after token refresh:', originalRequest.url);
313
+ setAuthHeader(originalRequest, token);
314
+ return userClient(originalRequest);
315
+ }).catch(err => {
316
+ _index2.Logger.error('Failed to retry request after token refresh:', getErrorMessage(err));
317
+ return Promise.reject(err);
318
+ });
319
+ }
320
+
321
+ // Start a new token refresh process and track it
322
+ refreshTokenPromise = (async () => {
323
+ try {
324
+ _index2.Logger.info('User Client: Refreshing token...');
325
+ const refreshToken = await (0, _authStore.getToken)('refreshToken');
326
+ if (!refreshToken || !refreshToken.token) {
327
+ _index2.Logger.error('No refresh token available or token is invalid');
328
+ throw new Error('No refresh token available');
329
+ }
330
+ _index2.Logger.info('Dispatching refresh token action');
331
+ const activeStore = requireStore();
332
+ const refreshResult = await activeStore.dispatch(authActions.refreshToken());
333
+ if (refreshResult.error) {
334
+ _index2.Logger.error('Token refresh action failed:', refreshResult.error);
335
+ throw new Error(`Token refresh failed: ${refreshResult.error.message || 'Unknown error'}`);
336
+ }
337
+ _index2.Logger.info('Getting new access token after refresh');
338
+ const newAccessToken = await (0, _authStore.getToken)('accessToken');
339
+ if (!newAccessToken?.token) {
340
+ throw new Error('Failed to retrieve new access token');
341
+ }
342
+ _index2.Logger.info('Token refreshed successfully');
343
+ processQueue(null, newAccessToken.token);
344
+ return newAccessToken.token;
345
+ } catch (refreshError) {
346
+ const responseData = _axios.default.isAxiosError(refreshError) ? refreshError.response?.data : undefined;
347
+ _index2.Logger.error('Token refresh failed:', responseData ?? getErrorMessage(refreshError));
348
+ processQueue(refreshError, null);
349
+ await requireStore().dispatch(authActions.logout());
350
+ throw refreshError;
351
+ } finally {
352
+ refreshTokenPromise = null;
353
+ }
354
+ })();
355
+ try {
356
+ // Wait for the refresh token process to complete
357
+ const newToken = await refreshTokenPromise;
358
+ setAuthHeader(originalRequest, newToken);
359
+ return userClient(originalRequest);
360
+ } catch (err) {
361
+ return Promise.reject(err);
362
+ }
363
+ }
364
+ _index2.Logger.error(`Response error: ${axiosError.response?.data ?? axiosError.message}, URL: ${originalRequest?.url}, Status: ${axiosError.response.status}`);
365
+ return Promise.reject(error);
366
+ });
367
+ var _default = exports.default = userClient;
368
+ //# sourceMappingURL=userClient.js.map
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.storeToken = exports.setKeychainImplementation = exports.removeToken = exports.getToken = void 0;
7
+ var _index = require("../logger/index.js");
8
+ /**
9
+ * Token data persisted in the keychain.
10
+ */
11
+
12
+ /**
13
+ * Minimal keychain shape we depend on.
14
+ * Lets us avoid a hard import on `react-native-keychain`, so an app that never
15
+ * stores a token does not have to install it.
16
+ */
17
+
18
+ let backend = null;
19
+ let resolved = false;
20
+ const resolveBackend = () => {
21
+ if (resolved) {
22
+ return backend;
23
+ }
24
+ resolved = true;
25
+ try {
26
+ const mod = require('react-native-keychain');
27
+ // Prefer named exports: the real module and the test mock both expose them
28
+ // flat, while `default` is a differently-shaped convenience object.
29
+ backend = typeof mod?.setInternetCredentials === 'function' ? mod : mod?.default;
30
+ } catch {
31
+ backend = null;
32
+ }
33
+ return backend;
34
+ };
35
+ const requireBackend = op => {
36
+ const b = resolveBackend();
37
+ if (!b) {
38
+ const err = new Error(`[authStore] react-native-keychain is not installed; cannot ${op}. ` + 'Install the peer dep or call setKeychainImplementation() with a custom backend.');
39
+ _index.Logger.error(err.message);
40
+ throw err;
41
+ }
42
+ return b;
43
+ };
44
+
45
+ /**
46
+ * Inject a custom keychain backend at runtime.
47
+ * Pass `null` to reset to the default react-native-keychain backend.
48
+ */
49
+ const setKeychainImplementation = impl => {
50
+ backend = impl;
51
+ resolved = impl !== null;
52
+ };
53
+
54
+ /**
55
+ * Store token with expiry.
56
+ */
57
+ exports.setKeychainImplementation = setKeychainImplementation;
58
+ const storeToken = async (key, token, expiry) => {
59
+ // Never log the token or expiry values: Logger forwards to whatever remote
60
+ // sink the app wired (Crashlytics/Sentry), so a token here leaves the device.
61
+ _index.Logger.info(`Storing token for key: ${key}`);
62
+ try {
63
+ const data = JSON.stringify({
64
+ token,
65
+ expiry
66
+ });
67
+ await requireBackend('store a token').setInternetCredentials(key, 'token', data, {
68
+ service: key
69
+ }); // Ensure service matches
70
+ _index.Logger.info(`Token stored successfully for key: ${key}`);
71
+ } catch (error) {
72
+ // Log AND rethrow. Swallowing this told the caller the token was persisted
73
+ // when it was not, so an app would show a signed-in user holding a token
74
+ // that never reached the keychain — the session then dies at the next
75
+ // cold start with no failure anyone could have seen.
76
+ _index.Logger.error(`Error storing token for key: ${key}`, error);
77
+ throw error;
78
+ }
79
+ };
80
+
81
+ /**
82
+ * Parse token data from credentials.
83
+ */
84
+ exports.storeToken = storeToken;
85
+ const parseTokenData = credentials => {
86
+ if (!credentials) {
87
+ return null;
88
+ }
89
+ const {
90
+ password
91
+ } = credentials;
92
+ try {
93
+ const parsed = JSON.parse(password);
94
+ if (parsed && typeof parsed === 'object' && 'token' in parsed && 'expiry' in parsed && typeof parsed.token === 'string' && typeof parsed.expiry === 'string') {
95
+ return parsed;
96
+ }
97
+ return null;
98
+ } catch (error) {
99
+ _index.Logger.error('Failed to parse token data', error);
100
+ return null;
101
+ }
102
+ };
103
+
104
+ /**
105
+ * Retrieve token and check if expired.
106
+ */
107
+ const getToken = async key => {
108
+ _index.Logger.info(`Retrieving token for key: ${key}`);
109
+ try {
110
+ const credentials = await requireBackend('read a token').getInternetCredentials(key, {
111
+ service: key
112
+ }); // Ensure service matches
113
+ const parsedData = parseTokenData(credentials);
114
+ _index.Logger.info(`Parsed token data for key: ${key}: ${parsedData ? 'present' : 'absent'}`);
115
+ if (!parsedData) {
116
+ _index.Logger.warn(`Invalid or no token data found for key: ${key}`);
117
+ return null;
118
+ }
119
+ const {
120
+ token,
121
+ expiry
122
+ } = parsedData;
123
+ _index.Logger.info(`Token retrieved for key: ${key}`);
124
+ return {
125
+ token,
126
+ expiry
127
+ };
128
+ } catch (error) {
129
+ const message = error instanceof Error ? error.message : String(error);
130
+ if (message.includes('Key permanently invalidated')) {
131
+ _index.Logger.error(`Key permanently invalidated for key: ${key}. Clearing the invalidated key.`);
132
+ await removeToken(key); // Clear the invalidated key
133
+ return null;
134
+ }
135
+ _index.Logger.error(`Error retrieving token for key: ${key}`, error);
136
+ return null;
137
+ }
138
+ };
139
+
140
+ /**
141
+ * Remove token by key.
142
+ */
143
+ exports.getToken = getToken;
144
+ const removeToken = async key => {
145
+ try {
146
+ await requireBackend('remove a token').resetInternetCredentials({
147
+ service: key
148
+ });
149
+ _index.Logger.info(`Token removed successfully for key: ${key}`);
150
+ } catch (error) {
151
+ _index.Logger.error(`Failed to remove token for key: ${key}`, error);
152
+ }
153
+ };
154
+ exports.removeToken = removeToken;
155
+ //# sourceMappingURL=authStore.js.map
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "decodeJWT", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _jwt.decodeJWT;
10
+ }
11
+ });
12
+ Object.defineProperty(exports, "getJWTExpiry", {
13
+ enumerable: true,
14
+ get: function () {
15
+ return _jwt.getJWTExpiry;
16
+ }
17
+ });
18
+ Object.defineProperty(exports, "getToken", {
19
+ enumerable: true,
20
+ get: function () {
21
+ return _authStore.getToken;
22
+ }
23
+ });
24
+ Object.defineProperty(exports, "isJWTExpired", {
25
+ enumerable: true,
26
+ get: function () {
27
+ return _jwt.isJWTExpired;
28
+ }
29
+ });
30
+ Object.defineProperty(exports, "removeToken", {
31
+ enumerable: true,
32
+ get: function () {
33
+ return _authStore.removeToken;
34
+ }
35
+ });
36
+ Object.defineProperty(exports, "storeToken", {
37
+ enumerable: true,
38
+ get: function () {
39
+ return _authStore.storeToken;
40
+ }
41
+ });
42
+ var _authStore = require("./authStore.js");
43
+ var _jwt = require("./jwt.js");
44
+ //# sourceMappingURL=index.js.map