@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
@@ -0,0 +1,360 @@
1
+ "use strict";
2
+
3
+ import axios, { AxiosHeaders } from 'axios';
4
+ import axiosRetry from 'axios-retry';
5
+ import { getToken } from "../auth/authStore.js";
6
+ import { getConfigValue } from "../config/index.js";
7
+ import { Logger } from "../logger/index.js";
8
+ import { getNetworkStatus } from "../network/networkStatus.js";
9
+
10
+ // Define timeout for requests
11
+ const REQUEST_TIMEOUT = 30000; // 30 seconds
12
+
13
+ /**
14
+ * Thrown by the request interceptor when the device is offline, before the
15
+ * request ever reaches the adapter (and therefore the network). Distinct from
16
+ * an AxiosError so callers can show an offline message instead of a generic
17
+ * request failure.
18
+ */
19
+ export class RequestOfflineError extends Error {
20
+ constructor(message = 'Request blocked: device is offline') {
21
+ super(message);
22
+ this.name = 'RequestOfflineError';
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Sub-codes a 401 response body carries when the session itself is dead
28
+ * (terminated by an admin, or expired server-side) rather than merely
29
+ * out-of-date. These never warrant a refresh attempt — only logout.
30
+ */
31
+ export const SESSION_TERMINATION_CODES = {
32
+ TERMINATED: 'SESSION_TERMINATED',
33
+ EXPIRED: 'SESSION_EXPIRED'
34
+ };
35
+ const TERMINATION_CODES = new Set(Object.values(SESSION_TERMINATION_CODES));
36
+
37
+ /**
38
+ * The header is the AUTHORITATIVE signal, verified against the backend:
39
+ * `WeborityJwtMiddleware.ReturnUnauthorized` answers an invalidated session with
40
+ * `401` + `X-Security-Stamp-Invalid: true` and a body of
41
+ * `{ error: 'Unauthorized', message: '...' }` — no code field at all, and
42
+ * `error` is a string rather than an object. An earlier version of this checked
43
+ * only body fields and would therefore never have fired against a real server.
44
+ *
45
+ * The body codes below are kept as a secondary path for any head that does send
46
+ * an explicit sub-code; they match on exact strings, so looking is free.
47
+ */
48
+ export const SESSION_INVALID_HEADER = 'x-security-stamp-invalid';
49
+ const hasSessionInvalidHeader = headers => {
50
+ if (!headers || typeof headers !== 'object') {
51
+ return false;
52
+ }
53
+ const get = headers.get;
54
+ const raw = typeof get === 'function' ? get.call(headers, SESSION_INVALID_HEADER) : headers[SESSION_INVALID_HEADER];
55
+ return String(raw).toLowerCase() === 'true';
56
+ };
57
+ const getSessionTerminationCode = data => {
58
+ if (!data || typeof data !== 'object') {
59
+ return undefined;
60
+ }
61
+ const body = data;
62
+ const nested = body.error;
63
+ const candidates = [body.code, body.errorCode, nested && typeof nested === 'object' ? nested.code : undefined];
64
+ for (const candidate of candidates) {
65
+ if (typeof candidate === 'string' && TERMINATION_CODES.has(candidate)) {
66
+ return candidate;
67
+ }
68
+ }
69
+ return undefined;
70
+ };
71
+
72
+ /**
73
+ * Minimal Redux-store shape required by the user client to dispatch
74
+ * refresh-token / logout actions.
75
+ */
76
+
77
+ /**
78
+ * Internal extension of axios request config to track retry attempts.
79
+ */
80
+
81
+ const userClient = axios.create({
82
+ baseURL: getConfigValue('baseUrl') ?? undefined,
83
+ headers: {
84
+ 'Content-Type': 'application/json',
85
+ 'Accept-Encoding': 'gzip, deflate, br'
86
+ },
87
+ timeout: REQUEST_TIMEOUT // Set timeout for all requests
88
+ });
89
+
90
+ // Configure axios-retry with modified exponential backoff
91
+ axiosRetry(userClient, {
92
+ retries: 3,
93
+ retryDelay: retryCount => retryCount === 0 ? 0 : Math.pow(2, retryCount) * 1000,
94
+ // Immediate retry for the first attempt
95
+ retryCondition: error => {
96
+ // Status-based ONLY — see publicClient for the reasoning. A bare network
97
+ // error carries no response, so it is not retried: the write may already
98
+ // have landed server-side, and this client issues non-idempotent requests.
99
+ const status = error.response?.status;
100
+ return typeof status === 'number' && (status === 429 || status >= 500 && status <= 599);
101
+ }
102
+ });
103
+ let store;
104
+ let authActions = {
105
+ logout: () => undefined,
106
+ refreshToken: () => undefined
107
+ };
108
+ export const injectStore = _store => {
109
+ store = _store;
110
+ };
111
+ export const injectAuthActions = actions => {
112
+ authActions = {
113
+ ...authActions,
114
+ ...actions
115
+ };
116
+ };
117
+
118
+ // Single promise to track token refresh
119
+ let refreshTokenPromise = null;
120
+
121
+ // Queue for failed requests
122
+ let failedQueue = [];
123
+
124
+ /**
125
+ * Processes the queue of failed requests.
126
+ */
127
+ const processQueue = (error, token = null) => {
128
+ failedQueue.forEach(({
129
+ resolve,
130
+ reject
131
+ }) => {
132
+ if (error) {
133
+ reject(error);
134
+ } else if (token !== null) {
135
+ resolve(token);
136
+ }
137
+ });
138
+ failedQueue = [];
139
+ };
140
+ const getErrorMessage = error => {
141
+ if (error instanceof Error) {
142
+ return error.message;
143
+ }
144
+ if (typeof error === 'string') {
145
+ return error;
146
+ }
147
+ return 'Unknown error';
148
+ };
149
+ const setAuthHeader = (config, token) => {
150
+ if (config.headers instanceof AxiosHeaders) {
151
+ config.headers.set('Authorization', `Bearer ${token}`);
152
+ } else {
153
+ // Fallback in case headers were provided as a plain object.
154
+ config.headers = AxiosHeaders.from({
155
+ ...config.headers,
156
+ Authorization: `Bearer ${token}`
157
+ });
158
+ }
159
+ };
160
+ const requireStore = () => {
161
+ if (!store) {
162
+ throw new Error('User package store is not initialized. Call initUserAuth first.');
163
+ }
164
+ return store;
165
+ };
166
+
167
+ /**
168
+ * Attach Authorization Token Before Requests
169
+ */
170
+ userClient.interceptors.request.use(async config => {
171
+ // Reject before any network call when the device is known to be offline.
172
+ // Unknown connectivity (netinfo not installed, or undetermined) is treated
173
+ // as online — never block a request on an unavailable probe.
174
+ const network = await getNetworkStatus();
175
+ if (network.isConnected === false) {
176
+ Logger.warn(`Request blocked: device is offline. URL: ${config.url}`);
177
+ return Promise.reject(new RequestOfflineError());
178
+ }
179
+ try {
180
+ // Skip token check for refresh token endpoint to prevent infinite loop
181
+ if (config.url?.includes('/Account/RefreshToken')) {
182
+ return config;
183
+ }
184
+ const token = await getToken('accessToken');
185
+
186
+ // Check if token exists
187
+ if (!token?.token) {
188
+ throw new Error('No access token available');
189
+ }
190
+
191
+ // Check if token is expired
192
+ const currentTime = Date.now();
193
+ const tokenExpiry = Date.parse(token.expiry);
194
+ const bufferTime = 30 * 1000; // 30 seconds buffer
195
+
196
+ // If token is expired or about to expire, try to refresh it first
197
+ if (tokenExpiry <= currentTime + bufferTime) {
198
+ Logger.info('Access token expired or about to expire, refreshing before request');
199
+
200
+ // If we already have a refresh in progress, wait for it
201
+ if (refreshTokenPromise) {
202
+ Logger.info('Token refresh already in progress, waiting for completion');
203
+ try {
204
+ const newToken = await refreshTokenPromise;
205
+ setAuthHeader(config, newToken);
206
+ return config;
207
+ } catch (err) {
208
+ Logger.error('Failed to use refreshed token:', getErrorMessage(err));
209
+ throw err;
210
+ }
211
+ }
212
+
213
+ // Start a new token refresh process
214
+ refreshTokenPromise = (async () => {
215
+ try {
216
+ const refreshToken = await getToken('refreshToken');
217
+ if (!refreshToken?.token) {
218
+ Logger.error('No refresh token available');
219
+ throw new Error('No refresh token available');
220
+ }
221
+
222
+ // Check if refresh token is expired
223
+ const refreshTokenExpiry = Date.parse(refreshToken.expiry);
224
+ if (refreshTokenExpiry <= currentTime) {
225
+ Logger.error('Refresh token expired');
226
+ throw new Error('Refresh token expired');
227
+ }
228
+ Logger.info('Dispatching refresh token action');
229
+ const activeStore = requireStore();
230
+ const refreshResult = await activeStore.dispatch(authActions.refreshToken());
231
+ if (refreshResult.error) {
232
+ throw new Error(`Token refresh failed: ${refreshResult.error.message || 'Unknown error'}`);
233
+ }
234
+ const newAccessToken = await getToken('accessToken');
235
+ if (!newAccessToken?.token) {
236
+ throw new Error('Failed to retrieve new access token after refresh');
237
+ }
238
+ Logger.info('Token refreshed successfully before request');
239
+ return newAccessToken.token;
240
+ } catch (error) {
241
+ Logger.error('Token refresh failed before request:', getErrorMessage(error));
242
+ await requireStore().dispatch(authActions.logout());
243
+ throw error;
244
+ } finally {
245
+ refreshTokenPromise = null;
246
+ }
247
+ })();
248
+ try {
249
+ // Wait for the refresh to complete
250
+ const newToken = await refreshTokenPromise;
251
+ setAuthHeader(config, newToken);
252
+ return config;
253
+ } catch (err) {
254
+ return Promise.reject(err);
255
+ }
256
+ }
257
+
258
+ // Token is valid, proceed with request
259
+ setAuthHeader(config, token.token);
260
+ return config;
261
+ } catch (error) {
262
+ Logger.error('Failed to attach token:', getErrorMessage(error));
263
+ return Promise.reject(error);
264
+ }
265
+ }, error => {
266
+ Logger.error('Request error:', getErrorMessage(error));
267
+ return Promise.reject(error);
268
+ });
269
+
270
+ /**
271
+ * Handle Response & Token Refresh
272
+ */
273
+ userClient.interceptors.response.use(response => response, async error => {
274
+ if (!axios.isAxiosError(error)) {
275
+ Logger.error(`Non-axios error in response interceptor: ${getErrorMessage(error)}`);
276
+ return Promise.reject(error);
277
+ }
278
+ const axiosError = error;
279
+ const originalRequest = axiosError.config;
280
+ if (!axiosError.response) {
281
+ Logger.error(`Network error or no response from server: ${axiosError.message}, URL: ${originalRequest?.url}`);
282
+ return Promise.reject(error);
283
+ }
284
+ if (axiosError.response.status === 401 && originalRequest && !originalRequest._retry) {
285
+ const terminationCode = getSessionTerminationCode(axiosError.response.data) ?? (hasSessionInvalidHeader(axiosError.response.headers) ? SESSION_TERMINATION_CODES.TERMINATED : undefined);
286
+ if (terminationCode) {
287
+ // The session is dead server-side — a refresh would only burn a
288
+ // pointless round-trip, so go straight to logout.
289
+ Logger.warn(`Session ${terminationCode}, logging out without refresh. URL: ${originalRequest.url}`);
290
+ await requireStore().dispatch(authActions.logout());
291
+ return Promise.reject(error);
292
+ }
293
+ Logger.warn(`Unauthorized URL: ${originalRequest.url}`);
294
+ originalRequest._retry = true;
295
+
296
+ // If we already have a refresh in progress, queue this request
297
+ if (refreshTokenPromise) {
298
+ return new Promise((resolve, reject) => {
299
+ failedQueue.push({
300
+ resolve,
301
+ reject
302
+ });
303
+ }).then(token => {
304
+ Logger.info('Retrying request after token refresh:', originalRequest.url);
305
+ setAuthHeader(originalRequest, token);
306
+ return userClient(originalRequest);
307
+ }).catch(err => {
308
+ Logger.error('Failed to retry request after token refresh:', getErrorMessage(err));
309
+ return Promise.reject(err);
310
+ });
311
+ }
312
+
313
+ // Start a new token refresh process and track it
314
+ refreshTokenPromise = (async () => {
315
+ try {
316
+ Logger.info('User Client: Refreshing token...');
317
+ const refreshToken = await getToken('refreshToken');
318
+ if (!refreshToken || !refreshToken.token) {
319
+ Logger.error('No refresh token available or token is invalid');
320
+ throw new Error('No refresh token available');
321
+ }
322
+ Logger.info('Dispatching refresh token action');
323
+ const activeStore = requireStore();
324
+ const refreshResult = await activeStore.dispatch(authActions.refreshToken());
325
+ if (refreshResult.error) {
326
+ Logger.error('Token refresh action failed:', refreshResult.error);
327
+ throw new Error(`Token refresh failed: ${refreshResult.error.message || 'Unknown error'}`);
328
+ }
329
+ Logger.info('Getting new access token after refresh');
330
+ const newAccessToken = await getToken('accessToken');
331
+ if (!newAccessToken?.token) {
332
+ throw new Error('Failed to retrieve new access token');
333
+ }
334
+ Logger.info('Token refreshed successfully');
335
+ processQueue(null, newAccessToken.token);
336
+ return newAccessToken.token;
337
+ } catch (refreshError) {
338
+ const responseData = axios.isAxiosError(refreshError) ? refreshError.response?.data : undefined;
339
+ Logger.error('Token refresh failed:', responseData ?? getErrorMessage(refreshError));
340
+ processQueue(refreshError, null);
341
+ await requireStore().dispatch(authActions.logout());
342
+ throw refreshError;
343
+ } finally {
344
+ refreshTokenPromise = null;
345
+ }
346
+ })();
347
+ try {
348
+ // Wait for the refresh token process to complete
349
+ const newToken = await refreshTokenPromise;
350
+ setAuthHeader(originalRequest, newToken);
351
+ return userClient(originalRequest);
352
+ } catch (err) {
353
+ return Promise.reject(err);
354
+ }
355
+ }
356
+ Logger.error(`Response error: ${axiosError.response?.data ?? axiosError.message}, URL: ${originalRequest?.url}, Status: ${axiosError.response.status}`);
357
+ return Promise.reject(error);
358
+ });
359
+ export default userClient;
360
+ //# sourceMappingURL=userClient.js.map
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+
3
+ import { Logger } from "../logger/index.js";
4
+
5
+ /**
6
+ * Token data persisted in the keychain.
7
+ */
8
+
9
+ /**
10
+ * Minimal keychain shape we depend on.
11
+ * Lets us avoid a hard import on `react-native-keychain`, so an app that never
12
+ * stores a token does not have to install it.
13
+ */
14
+
15
+ let backend = null;
16
+ let resolved = false;
17
+ const resolveBackend = () => {
18
+ if (resolved) {
19
+ return backend;
20
+ }
21
+ resolved = true;
22
+ try {
23
+ const mod = require('react-native-keychain');
24
+ // Prefer named exports: the real module and the test mock both expose them
25
+ // flat, while `default` is a differently-shaped convenience object.
26
+ backend = typeof mod?.setInternetCredentials === 'function' ? mod : mod?.default;
27
+ } catch {
28
+ backend = null;
29
+ }
30
+ return backend;
31
+ };
32
+ const requireBackend = op => {
33
+ const b = resolveBackend();
34
+ if (!b) {
35
+ const err = new Error(`[authStore] react-native-keychain is not installed; cannot ${op}. ` + 'Install the peer dep or call setKeychainImplementation() with a custom backend.');
36
+ Logger.error(err.message);
37
+ throw err;
38
+ }
39
+ return b;
40
+ };
41
+
42
+ /**
43
+ * Inject a custom keychain backend at runtime.
44
+ * Pass `null` to reset to the default react-native-keychain backend.
45
+ */
46
+ export const setKeychainImplementation = impl => {
47
+ backend = impl;
48
+ resolved = impl !== null;
49
+ };
50
+
51
+ /**
52
+ * Store token with expiry.
53
+ */
54
+ export const storeToken = async (key, token, expiry) => {
55
+ // Never log the token or expiry values: Logger forwards to whatever remote
56
+ // sink the app wired (Crashlytics/Sentry), so a token here leaves the device.
57
+ Logger.info(`Storing token for key: ${key}`);
58
+ try {
59
+ const data = JSON.stringify({
60
+ token,
61
+ expiry
62
+ });
63
+ await requireBackend('store a token').setInternetCredentials(key, 'token', data, {
64
+ service: key
65
+ }); // Ensure service matches
66
+ Logger.info(`Token stored successfully for key: ${key}`);
67
+ } catch (error) {
68
+ // Log AND rethrow. Swallowing this told the caller the token was persisted
69
+ // when it was not, so an app would show a signed-in user holding a token
70
+ // that never reached the keychain — the session then dies at the next
71
+ // cold start with no failure anyone could have seen.
72
+ Logger.error(`Error storing token for key: ${key}`, error);
73
+ throw error;
74
+ }
75
+ };
76
+
77
+ /**
78
+ * Parse token data from credentials.
79
+ */
80
+ const parseTokenData = credentials => {
81
+ if (!credentials) {
82
+ return null;
83
+ }
84
+ const {
85
+ password
86
+ } = credentials;
87
+ try {
88
+ const parsed = JSON.parse(password);
89
+ if (parsed && typeof parsed === 'object' && 'token' in parsed && 'expiry' in parsed && typeof parsed.token === 'string' && typeof parsed.expiry === 'string') {
90
+ return parsed;
91
+ }
92
+ return null;
93
+ } catch (error) {
94
+ Logger.error('Failed to parse token data', error);
95
+ return null;
96
+ }
97
+ };
98
+
99
+ /**
100
+ * Retrieve token and check if expired.
101
+ */
102
+ export const getToken = async key => {
103
+ Logger.info(`Retrieving token for key: ${key}`);
104
+ try {
105
+ const credentials = await requireBackend('read a token').getInternetCredentials(key, {
106
+ service: key
107
+ }); // Ensure service matches
108
+ const parsedData = parseTokenData(credentials);
109
+ Logger.info(`Parsed token data for key: ${key}: ${parsedData ? 'present' : 'absent'}`);
110
+ if (!parsedData) {
111
+ Logger.warn(`Invalid or no token data found for key: ${key}`);
112
+ return null;
113
+ }
114
+ const {
115
+ token,
116
+ expiry
117
+ } = parsedData;
118
+ Logger.info(`Token retrieved for key: ${key}`);
119
+ return {
120
+ token,
121
+ expiry
122
+ };
123
+ } catch (error) {
124
+ const message = error instanceof Error ? error.message : String(error);
125
+ if (message.includes('Key permanently invalidated')) {
126
+ Logger.error(`Key permanently invalidated for key: ${key}. Clearing the invalidated key.`);
127
+ await removeToken(key); // Clear the invalidated key
128
+ return null;
129
+ }
130
+ Logger.error(`Error retrieving token for key: ${key}`, error);
131
+ return null;
132
+ }
133
+ };
134
+
135
+ /**
136
+ * Remove token by key.
137
+ */
138
+ export const removeToken = async key => {
139
+ try {
140
+ await requireBackend('remove a token').resetInternetCredentials({
141
+ service: key
142
+ });
143
+ Logger.info(`Token removed successfully for key: ${key}`);
144
+ } catch (error) {
145
+ Logger.error(`Failed to remove token for key: ${key}`, error);
146
+ }
147
+ };
148
+ //# sourceMappingURL=authStore.js.map
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ export { getToken, removeToken, storeToken } from "./authStore.js";
4
+ export { decodeJWT, getJWTExpiry, isJWTExpired } from "./jwt.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+
3
+ import { Logger } from "../logger/index.js";
4
+
5
+ /**
6
+ * Standard claims plus arbitrary keys.
7
+ * `sub`, `exp`, `iat`, `nbf` follow RFC 7519 conventions.
8
+ */
9
+
10
+ const base64UrlToBase64 = input => {
11
+ const padded = input + '='.repeat((4 - input.length % 4) % 4);
12
+ return padded.replace(/-/g, '+').replace(/_/g, '/');
13
+ };
14
+ const decodeBase64 = input => {
15
+ // RN runtimes expose `atob` (Hermes 0.71+); fall back to a Buffer-based decode otherwise.
16
+ if (typeof atob === 'function') {
17
+ return atob(input);
18
+ }
19
+ const {
20
+ Buffer
21
+ } = require('buffer');
22
+ return Buffer.from(input, 'base64').toString('utf-8');
23
+ };
24
+ const utf8FromBinary = binary => {
25
+ // Convert percent-encoded UTF-8 sequence so multi-byte chars survive.
26
+ try {
27
+ return decodeURIComponent(binary.split('').map(c => '%' + c.charCodeAt(0).toString(16).padStart(2, '0')).join(''));
28
+ } catch {
29
+ return binary;
30
+ }
31
+ };
32
+
33
+ /**
34
+ * Decode a JWT's payload claims. Does **not** verify the signature.
35
+ *
36
+ * @returns claims object on success, `null` if the token is malformed.
37
+ */
38
+ export const decodeJWT = token => {
39
+ if (!token || typeof token !== 'string') {
40
+ return null;
41
+ }
42
+ const parts = token.split('.');
43
+ if (parts.length !== 3) {
44
+ Logger.warn('[jwt] decodeJWT: token is not a valid JWT (expected 3 segments)');
45
+ return null;
46
+ }
47
+ try {
48
+ const payload = parts[1];
49
+ const json = utf8FromBinary(decodeBase64(base64UrlToBase64(payload)));
50
+ return JSON.parse(json);
51
+ } catch (error) {
52
+ Logger.warn('[jwt] decodeJWT failed to parse payload', error);
53
+ return null;
54
+ }
55
+ };
56
+
57
+ /**
58
+ * Returns true when the token is expired (or unparseable / missing `exp`).
59
+ *
60
+ * @param token JWT string
61
+ * @param leewaySeconds amount of clock-skew tolerance to apply (default 30s)
62
+ */
63
+ export const isJWTExpired = (token, leewaySeconds = 30) => {
64
+ const claims = decodeJWT(token);
65
+ if (!claims || typeof claims.exp !== 'number') {
66
+ return true;
67
+ }
68
+ const nowSeconds = Math.floor(Date.now() / 1000);
69
+ return claims.exp <= nowSeconds + leewaySeconds;
70
+ };
71
+
72
+ /**
73
+ * Returns the token's expiry as a Date, or `null` when no `exp` claim exists.
74
+ */
75
+ export const getJWTExpiry = token => {
76
+ const claims = decodeJWT(token);
77
+ if (!claims || typeof claims.exp !== 'number') {
78
+ return null;
79
+ }
80
+ return new Date(claims.exp * 1000);
81
+ };
82
+ //# sourceMappingURL=jwt.js.map
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Configuration management for Webority packages
5
+ * Provides a flexible way to inject configuration without hard dependencies
6
+ */
7
+
8
+ const DEFAULT_CONFIG = {
9
+ baseUrl: null,
10
+ loggingEnabled: true,
11
+ logLevel: 'info',
12
+ environment: 'Development',
13
+ country: 'in',
14
+ appId: null,
15
+ appVersion: null
16
+ };
17
+ let globalConfig = {
18
+ ...DEFAULT_CONFIG
19
+ };
20
+
21
+ /**
22
+ * Set global configuration for all Webority packages
23
+ */
24
+ export const setConfig = config => {
25
+ globalConfig = {
26
+ ...globalConfig,
27
+ ...config
28
+ };
29
+ };
30
+
31
+ /**
32
+ * Get current configuration
33
+ */
34
+ export const getConfig = () => globalConfig;
35
+
36
+ /**
37
+ * Get a specific config value with optional fallback.
38
+ * If the stored value is `null`, the fallback is returned (when provided).
39
+ */
40
+
41
+ export function getConfigValue(key, fallback) {
42
+ const value = globalConfig[key];
43
+ if (value !== null) {
44
+ return value;
45
+ }
46
+ return fallback;
47
+ }
48
+
49
+ /**
50
+ * Reset configuration to defaults
51
+ */
52
+ export const resetConfig = () => {
53
+ globalConfig = {
54
+ ...DEFAULT_CONFIG
55
+ };
56
+ };
57
+ //# sourceMappingURL=index.js.map