@webority/mobile-core 0.0.28 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/lib/commonjs/api/baseUrl.js +1 -34
  2. package/lib/commonjs/api/curl.js +1 -109
  3. package/lib/commonjs/api/multipart.js +1 -21
  4. package/lib/commonjs/api/publicClient.js +1 -79
  5. package/lib/commonjs/api/responseBodyText.js +1 -0
  6. package/lib/commonjs/api/retry.js +1 -91
  7. package/lib/commonjs/api/tokenRefresh.js +1 -205
  8. package/lib/commonjs/api/userClient.js +1 -265
  9. package/lib/commonjs/auth/authStore.js +1 -265
  10. package/lib/commonjs/auth/index.js +1 -44
  11. package/lib/commonjs/auth/jwt.js +1 -112
  12. package/lib/commonjs/config/index.js +1 -72
  13. package/lib/commonjs/deepLink/index.js +1 -286
  14. package/lib/commonjs/download/adapters/expoFs.js +1 -69
  15. package/lib/commonjs/download/adapters/shared.js +1 -13
  16. package/lib/commonjs/download/index.js +1 -250
  17. package/lib/commonjs/formatters/currency.js +1 -70
  18. package/lib/commonjs/formatters/date.js +1 -167
  19. package/lib/commonjs/formatters/dateMath.js +1 -311
  20. package/lib/commonjs/formatters/index.js +1 -246
  21. package/lib/commonjs/formatters/initials.js +1 -32
  22. package/lib/commonjs/formatters/intlCache.js +1 -37
  23. package/lib/commonjs/formatters/number.js +1 -89
  24. package/lib/commonjs/formatters/phone.js +1 -369
  25. package/lib/commonjs/formatters/windowsToIanaTable.js +1 -164
  26. package/lib/commonjs/geolocation/adapters/expoLocation.js +1 -126
  27. package/lib/commonjs/geolocation/index.js +1 -355
  28. package/lib/commonjs/index.js +1 -644
  29. package/lib/commonjs/initSDK.js +1 -66
  30. package/lib/commonjs/initUserAuth.js +1 -37
  31. package/lib/commonjs/logger/crashlytics.js +1 -0
  32. package/lib/commonjs/logger/index.js +1 -301
  33. package/lib/commonjs/logger/redact.js +1 -50
  34. package/lib/commonjs/network/index.js +1 -31
  35. package/lib/commonjs/network/networkStatus.js +1 -171
  36. package/lib/commonjs/permissions/calendar.js +1 -14
  37. package/lib/commonjs/permissions/camera.js +1 -14
  38. package/lib/commonjs/permissions/contacts.js +1 -10
  39. package/lib/commonjs/permissions/index.js +1 -133
  40. package/lib/commonjs/permissions/location.js +1 -14
  41. package/lib/commonjs/permissions/mediaLibrary.js +1 -10
  42. package/lib/commonjs/permissions/notifications.js +1 -10
  43. package/lib/commonjs/permissions/photos.js +1 -14
  44. package/lib/commonjs/phone/countries.js +1 -265
  45. package/lib/commonjs/push/index.js +1 -61
  46. package/lib/commonjs/push/push.js +1 -443
  47. package/lib/commonjs/push/status.js +1 -0
  48. package/lib/commonjs/push/task.js +1 -43
  49. package/lib/commonjs/secureScreen/index.js +1 -0
  50. package/lib/commonjs/share/index.js +1 -70
  51. package/lib/commonjs/smsRetriever/index.js +1 -91
  52. package/lib/commonjs/specs/NativeSmsRetriever.js +1 -15
  53. package/lib/commonjs/sqlite/adapters/expo.js +1 -53
  54. package/lib/commonjs/sqlite/index.js +1 -604
  55. package/lib/commonjs/storage/index.js +1 -214
  56. package/lib/commonjs/types/axios.js +1 -4
  57. package/lib/commonjs/types/globals.d.js +1 -6
  58. package/lib/commonjs/utils/base64.js +1 -24
  59. package/lib/commonjs/utils/imageCompression.js +1 -389
  60. package/lib/commonjs/utils/index.js +1 -19
  61. package/lib/commonjs/validators/index.js +1 -227
  62. package/lib/commonjs/versionCheck/index.js +1 -419
  63. package/lib/module/api/baseUrl.js +1 -30
  64. package/lib/module/api/curl.js +1 -104
  65. package/lib/module/api/multipart.js +1 -16
  66. package/lib/module/api/publicClient.js +1 -75
  67. package/lib/module/api/responseBodyText.js +1 -0
  68. package/lib/module/api/retry.js +1 -85
  69. package/lib/module/api/tokenRefresh.js +1 -192
  70. package/lib/module/api/userClient.js +1 -243
  71. package/lib/module/auth/authStore.js +1 -257
  72. package/lib/module/auth/index.js +1 -5
  73. package/lib/module/auth/jwt.js +1 -106
  74. package/lib/module/config/index.js +1 -64
  75. package/lib/module/deepLink/index.js +1 -280
  76. package/lib/module/download/adapters/expoFs.js +1 -64
  77. package/lib/module/download/adapters/shared.js +1 -8
  78. package/lib/module/download/index.js +1 -242
  79. package/lib/module/formatters/currency.js +1 -65
  80. package/lib/module/formatters/date.js +1 -159
  81. package/lib/module/formatters/dateMath.js +1 -295
  82. package/lib/module/formatters/index.js +1 -9
  83. package/lib/module/formatters/initials.js +1 -27
  84. package/lib/module/formatters/intlCache.js +1 -30
  85. package/lib/module/formatters/number.js +1 -82
  86. package/lib/module/formatters/phone.js +1 -302
  87. package/lib/module/formatters/windowsToIanaTable.js +1 -160
  88. package/lib/module/geolocation/adapters/expoLocation.js +1 -121
  89. package/lib/module/geolocation/index.js +1 -347
  90. package/lib/module/index.js +1 -57
  91. package/lib/module/initSDK.js +1 -61
  92. package/lib/module/initUserAuth.js +1 -33
  93. package/lib/module/logger/crashlytics.js +1 -0
  94. package/lib/module/logger/index.js +1 -295
  95. package/lib/module/logger/redact.js +1 -44
  96. package/lib/module/network/index.js +1 -4
  97. package/lib/module/network/networkStatus.js +1 -161
  98. package/lib/module/permissions/calendar.js +1 -15
  99. package/lib/module/permissions/camera.js +1 -15
  100. package/lib/module/permissions/contacts.js +1 -11
  101. package/lib/module/permissions/index.js +1 -128
  102. package/lib/module/permissions/location.js +1 -15
  103. package/lib/module/permissions/mediaLibrary.js +1 -11
  104. package/lib/module/permissions/notifications.js +1 -11
  105. package/lib/module/permissions/photos.js +1 -15
  106. package/lib/module/phone/countries.js +1 -261
  107. package/lib/module/push/index.js +1 -4
  108. package/lib/module/push/push.js +1 -427
  109. package/lib/module/push/status.js +1 -0
  110. package/lib/module/push/task.js +1 -38
  111. package/lib/module/secureScreen/index.js +1 -0
  112. package/lib/module/share/index.js +1 -64
  113. package/lib/module/smsRetriever/index.js +1 -86
  114. package/lib/module/specs/NativeSmsRetriever.js +1 -13
  115. package/lib/module/sqlite/adapters/expo.js +1 -48
  116. package/lib/module/sqlite/index.js +1 -594
  117. package/lib/module/storage/index.js +1 -208
  118. package/lib/module/types/axios.js +1 -4
  119. package/lib/module/types/globals.d.js +1 -10
  120. package/lib/module/utils/base64.js +1 -19
  121. package/lib/module/utils/imageCompression.js +1 -383
  122. package/lib/module/utils/index.js +1 -4
  123. package/lib/module/validators/index.js +1 -205
  124. package/lib/module/versionCheck/index.js +1 -413
  125. package/lib/typescript/commonjs/api/responseBodyText.d.ts +6 -0
  126. package/lib/typescript/commonjs/index.d.ts +5 -0
  127. package/lib/typescript/commonjs/logger/crashlytics.d.ts +31 -0
  128. package/lib/typescript/commonjs/push/index.d.ts +3 -1
  129. package/lib/typescript/commonjs/push/push.d.ts +9 -1
  130. package/lib/typescript/commonjs/push/status.d.ts +9 -0
  131. package/lib/typescript/commonjs/secureScreen/index.d.ts +25 -0
  132. package/lib/typescript/module/api/responseBodyText.d.ts +6 -0
  133. package/lib/typescript/module/index.d.ts +5 -0
  134. package/lib/typescript/module/logger/crashlytics.d.ts +31 -0
  135. package/lib/typescript/module/push/index.d.ts +3 -1
  136. package/lib/typescript/module/push/push.d.ts +9 -1
  137. package/lib/typescript/module/push/status.d.ts +9 -0
  138. package/lib/typescript/module/secureScreen/index.d.ts +25 -0
  139. package/package.json +23 -4
@@ -1,30 +1 @@
1
- "use strict";
2
-
3
- import { getConfigValue } from "../config/index.js";
4
-
5
- /**
6
- * Resolves `baseURL` PER REQUEST rather than at module evaluation.
7
- *
8
- * `axios.create({ baseURL: getConfigValue('baseUrl') })` reads the value once,
9
- * when the module is first imported — which happens the moment anything touches
10
- * the package barrel, long before an app's startup code can call `setConfig`.
11
- * The result was a client permanently pinned to `undefined` and a `setConfig`
12
- * that appeared to do nothing, with no error anywhere: requests just went to a
13
- * relative URL.
14
- *
15
- * A consumer cannot fix that by ordering its own imports, because the import
16
- * that loses the race is usually one it never wrote. So the clients stop
17
- * capturing the value and ask for it per request instead.
18
- *
19
- * An explicit per-request `baseURL` still wins, which is what keeps a one-off
20
- * call to a different host possible.
21
- */
22
- export const applyConfiguredBaseUrl = client => {
23
- client.interceptors.request.use(config => {
24
- if (!config.baseURL) {
25
- config.baseURL = getConfigValue('baseUrl') ?? undefined;
26
- }
27
- return config;
28
- });
29
- };
30
- //# sourceMappingURL=baseUrl.js.map
1
+ import{getConfigValue as e}from"../config/index.js";export const applyConfiguredBaseUrl=r=>{r.interceptors.request.use(r=>(r.baseURL||(r.baseURL=e("baseUrl")??void 0),r))};
@@ -1,104 +1 @@
1
- "use strict";
2
-
3
- import { Logger } from "../logger/index.js";
4
-
5
- /**
6
- * Header names whose VALUE is a credential. Matched case-insensitively against
7
- * the whole name, since `Authorization` and `authorization` are the same header
8
- * and axios does not normalise the case of headers a caller sets by hand.
9
- */
10
- const SECRET_HEADERS = new Set(['authorization', 'proxy-authorization', 'cookie', 'set-cookie', 'x-api-key', 'x-auth-token', 'x-access-token', 'x-refresh-token', 'api-key']);
11
-
12
- /**
13
- * Body fields whose value is a credential. Matched on the key alone, so this
14
- * catches `password`, `newPassword` and `password_confirmation` alike.
15
- */
16
- const SECRET_BODY_PATTERN = /pass(word|code)|secret|token|otp|pin|cvv|authoriz/i;
17
-
18
- /** The axios per-method header buckets, which are config plumbing, not headers. */
19
- const AXIOS_METHOD_KEYS = new Set(['common', 'delete', 'get', 'head', 'post', 'put', 'patch']);
20
- const REDACTED = '<redacted>';
21
- const redactBody = (value, seen, depth = 0) => {
22
- if (value === null || typeof value !== 'object') {
23
- return value;
24
- }
25
- // Both guards are needed and they catch different shapes. `seen` breaks a
26
- // true cycle, which would otherwise recurse forever AND make the eventual
27
- // JSON.stringify throw. The depth bound catches a body that is merely
28
- // enormous. A curl line is a debugging aid, and it must never be able to
29
- // hang or fail the request it is describing.
30
- if (seen.has(value)) {
31
- return '<circular>';
32
- }
33
- if (depth > 6) {
34
- return '<truncated>';
35
- }
36
- seen.add(value);
37
- if (Array.isArray(value)) {
38
- return value.map(entry => redactBody(entry, seen, depth + 1));
39
- }
40
- const out = {};
41
- for (const [key, entry] of Object.entries(value)) {
42
- out[key] = SECRET_BODY_PATTERN.test(key) ? REDACTED : redactBody(entry, seen, depth + 1);
43
- }
44
- return out;
45
- };
46
- const buildUrl = config => {
47
- const url = config.url ?? '';
48
- const base = config.baseURL ? config.baseURL.replace(/\/$/, '') : '';
49
- let complete = url.startsWith('http') ? url : `${base}/${url.replace(/^\//, '')}`;
50
- if (config.params) {
51
- const query = new URLSearchParams(config.params).toString();
52
- if (query) {
53
- complete += (complete.includes('?') ? '&' : '?') + query;
54
- }
55
- }
56
- return complete;
57
- };
58
-
59
- /**
60
- * Builds a copy-pasteable curl command for a request, with every credential
61
- * replaced by a placeholder.
62
- *
63
- * The redaction is the point, not a nicety. An unredacted version of this in a
64
- * consumer app put `Authorization: Bearer <token>` and the login request's
65
- * plaintext password into the device console of a *release* staging build.
66
- * A curl line is worth having; a curl line that has to be trusted not to be
67
- * enabled in the wrong build is not.
68
- */
69
- export const buildCurl = config => {
70
- const method = (config.method ?? 'get').toUpperCase();
71
- let command = `curl -X ${method} "${buildUrl(config)}"`;
72
- const headers = config.headers;
73
- if (headers) {
74
- for (const [key, value] of Object.entries(headers)) {
75
- if (AXIOS_METHOD_KEYS.has(key)) {
76
- continue;
77
- }
78
- if (typeof value !== 'string' && typeof value !== 'number') {
79
- continue;
80
- }
81
- const shown = SECRET_HEADERS.has(key.toLowerCase()) ? REDACTED : value;
82
- command += ` -H "${key}: ${shown}"`;
83
- }
84
- }
85
- if (config.data !== undefined && config.data !== null) {
86
- const redacted = typeof config.data === 'object' ? redactBody(config.data, new WeakSet()) : config.data;
87
- const body = typeof redacted === 'object' ? JSON.stringify(redacted) : String(redacted);
88
- command += ` -d '${body.replace(/'/g, "'\\''")}'`;
89
- }
90
- return command;
91
- };
92
-
93
- /**
94
- * Logs the curl equivalent of a request. Never throws: a failure to describe a
95
- * request must not be able to fail the request itself.
96
- */
97
- export const logCurl = config => {
98
- try {
99
- Logger.info('CURL Command:', buildCurl(config));
100
- } catch (error) {
101
- Logger.warn('[api] Failed to build a curl command for the request', error);
102
- }
103
- };
104
- //# sourceMappingURL=curl.js.map
1
+ import{Logger as t}from"../logger/index.js";const e=new Set(["authorization","proxy-authorization","cookie","set-cookie","x-api-key","x-auth-token","x-access-token","x-refresh-token","api-key"]),r=/pass(word|code)|secret|token|otp|pin|cvv|authoriz/i,o=new Set(["common","delete","get","head","post","put","patch"]),a="<redacted>",redactBody=(t,e,o=0)=>{if(null===t||"object"!=typeof t)return t;if(e.has(t))return"<circular>";if(o>6)return"<truncated>";if(e.add(t),Array.isArray(t))return t.map(t=>redactBody(t,e,o+1));const n={};for(const[i,c]of Object.entries(t))n[i]=r.test(i)?a:redactBody(c,e,o+1);return n};export const buildCurl=t=>{let r=`curl -X ${(t.method??"get").toUpperCase()} "${(t=>{const e=t.url??"",r=t.baseURL?t.baseURL.replace(/\/$/,""):"";let o=e.startsWith("http")?e:`${r}/${e.replace(/^\//,"")}`;if(t.params){const e=new URLSearchParams(t.params).toString();e&&(o+=(o.includes("?")?"&":"?")+e)}return o})(t)}"`;const n=t.headers;if(n)for(const[t,i]of Object.entries(n)){if(o.has(t))continue;if("string"!=typeof i&&"number"!=typeof i)continue;const n=e.has(t.toLowerCase())?a:i;r+=` -H "${t}: ${n}"`}if(void 0!==t.data&&null!==t.data){const e="object"==typeof t.data?redactBody(t.data,new WeakSet):t.data;r+=` -d '${("object"==typeof e?JSON.stringify(e):String(e)).replace(/'/g,"'\\''")}'`}return r};export const logCurl=e=>{try{t.info("CURL Command:",buildCurl(e))}catch(e){t.warn("[api] Failed to build a curl command for the request",e)}};
@@ -1,16 +1 @@
1
- "use strict";
2
-
3
- /**
4
- * The clients default to a JSON content type, and axios then serialises a FormData body to
5
- * JSON, which breaks every upload. Dropping the header for FormData lets the platform write
6
- * the multipart header with its boundary.
7
- */
8
- export const sendFormDataAsMultipart = client => {
9
- client.interceptors.request.use(config => {
10
- if (typeof FormData !== 'undefined' && config.data instanceof FormData) {
11
- config.headers?.delete?.('Content-Type');
12
- }
13
- return config;
14
- });
15
- };
16
- //# sourceMappingURL=multipart.js.map
1
+ export const sendFormDataAsMultipart=e=>{e.interceptors.request.use(e=>("undefined"!=typeof FormData&&e.data instanceof FormData&&e.headers?.delete?.("Content-Type"),e))};
@@ -1,75 +1 @@
1
- "use strict";
2
-
3
- import axios from 'axios';
4
- import { getConfigValue } from "../config/index.js";
5
- import { Logger } from "../logger/index.js";
6
- import "../types/axios.js";
7
- import { applyConfiguredBaseUrl } from "./baseUrl.js";
8
- import { logCurl } from "./curl.js";
9
- import { sendFormDataAsMultipart } from "./multipart.js";
10
- import { attachRetry } from "./retry.js";
11
-
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.create({
23
- headers: {
24
- 'Content-Type': 'application/json',
25
- Accept: 'application/json',
26
- 'Accept-Encoding': 'gzip, deflate, br'
27
- },
28
- timeout: REQUEST_TIMEOUT
29
- });
30
- applyConfiguredBaseUrl(publicClient);
31
- sendFormDataAsMultipart(publicClient);
32
-
33
- // Registered before the logging interceptors below so they only ever observe
34
- // the final outcome of a request, not its intermediate retries. The policy
35
- // itself, and why a bare network error is never retried, lives in ./retry.
36
- attachRetry(publicClient);
37
-
38
- // Add request interceptor for logging
39
- publicClient.interceptors.request.use(config => {
40
- Logger.info(`Request: ${config.method?.toUpperCase()} ${config.url}`);
41
- if (getConfigValue('logCurl')) {
42
- logCurl(config);
43
- }
44
- return config;
45
- }, error => {
46
- const message = error instanceof Error ? error.message : String(error);
47
- Logger.error('Request error:', message);
48
- return Promise.reject(error);
49
- });
50
-
51
- // Add response interceptor for error handling
52
- publicClient.interceptors.response.use(response => {
53
- Logger.info(`Response: ${response.status} from ${response.config.url}`);
54
- return response;
55
- }, error => {
56
- if (axios.isAxiosError(error)) {
57
- const originalRequest = error.config;
58
- if (originalRequest?.skipErrorLog) {
59
- return Promise.reject(error);
60
- }
61
- if (!error.response) {
62
- Logger.error(`Network error or no response from server: ${error.message}, URL: ${originalRequest?.url}`);
63
- } else {
64
- const body = error.response.data;
65
- const bodyText = body instanceof ArrayBuffer ? `binary response (${body.byteLength} bytes)` : typeof Blob !== 'undefined' && body instanceof Blob ? `binary response (${body.size} bytes)` : body ?? error.message;
66
- Logger.error(`Response error: ${bodyText}, URL: ${originalRequest?.url}, Status: ${error.response.status}`);
67
- }
68
- } else {
69
- const message = error instanceof Error ? error.message : String(error);
70
- Logger.error(`Unknown response error: ${message}`);
71
- }
72
- return Promise.reject(error);
73
- });
74
- export default publicClient;
75
- //# sourceMappingURL=publicClient.js.map
1
+ import r from"axios";import{getConfigValue as e}from"../config/index.js";import{Logger as o}from"../logger/index.js";import"../types/axios.js";import{applyConfiguredBaseUrl as s}from"./baseUrl.js";import{logCurl as t}from"./curl.js";import{sendFormDataAsMultipart as n}from"./multipart.js";import{responseBodyText as i}from"./responseBodyText.js";import{attachRetry as p}from"./retry.js";const m=r.create({headers:{"Content-Type":"application/json",Accept:"application/json"},timeout:3e4});s(m),n(m),p(m),m.interceptors.request.use(r=>(o.info(`Request: ${r.method?.toUpperCase()} ${r.url}`),e("logCurl")&&t(r),r),r=>{const e=r instanceof Error?r.message:String(r);return o.error("Request error:",e),Promise.reject(r)}),m.interceptors.response.use(r=>(o.info(`Response: ${r.status} from ${r.config.url}`),r),e=>{if(r.isAxiosError(e)){const r=e.config;if(r?.skipErrorLog)return Promise.reject(e);e.response?o.error(`Response error: ${i(e.response.data,e.message)}, URL: ${r?.url}, Status: ${e.response.status}`):o.error(`Network error or no response from server: ${e.message}, URL: ${r?.url}`)}else{const r=e instanceof Error?e.message:String(e);o.error(`Unknown response error: ${r}`)}return Promise.reject(e)});export default m;
@@ -0,0 +1 @@
1
+ export const responseBodyText=(e,n)=>{if(null==e||""===e)return n;if(e instanceof ArrayBuffer)return`binary response (${e.byteLength} bytes)`;if("undefined"!=typeof Blob&&e instanceof Blob)return`binary response (${e.size} bytes)`;const r="string"==typeof e?e:JSON.stringify(e);return r.length>500?`${r.slice(0,500)}...`:r};
@@ -1,85 +1 @@
1
- "use strict";
2
-
3
- /**
4
- * Status-based ONLY, deliberately: a bare network error (no `error.response`)
5
- * is never retryable. Both clients carry non-idempotent requests, login and OTP
6
- * on the public one and every write on the user one, and a broken connection
7
- * says nothing about whether the request reached the server. Retrying can
8
- * therefore create a second session, or a duplicate record, that the user never
9
- * sees. A generic network-error retry rule is not a substitute: it decides on
10
- * the transport alone and never looks at the HTTP method, so it retries POSTs
11
- * as readily as GETs.
12
- */
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
- };
30
- const DEFAULT_POLICY = {
31
- retries: 3,
32
- retryDelayMs: attempt => attempt <= 1 ? 0 : 2 ** attempt * 1000,
33
- shouldRetry: isRetryableFailure,
34
- onRetry: () => undefined
35
- };
36
- const waitOrAbort = (delay, signal) => new Promise(resolve => {
37
- const onAbort = () => {
38
- clearTimeout(timer);
39
- resolve(false);
40
- };
41
- const timer = setTimeout(() => {
42
- signal?.removeEventListener?.('abort', onAbort);
43
- resolve(true);
44
- }, delay);
45
- signal?.addEventListener?.('abort', onAbort, {
46
- once: true
47
- });
48
- });
49
- export const attachRetry = (client, policy = {}) => {
50
- client.interceptors.response.use(undefined, async error => {
51
- const axiosError = error;
52
- const config = axiosError?.config;
53
- if (!config || config.retry === false) {
54
- return Promise.reject(error);
55
- }
56
- const perRequest = typeof config.retry === 'object' ? config.retry : {};
57
- const effective = {
58
- ...DEFAULT_POLICY,
59
- ...policy,
60
- ...perRequest
61
- };
62
- const attempt = (config.__retryAttempt ?? 0) + 1;
63
- if (attempt > effective.retries || !effective.shouldRetry(axiosError)) {
64
- return Promise.reject(error);
65
- }
66
- if (config.signal?.aborted) {
67
- return Promise.reject(error);
68
- }
69
-
70
- // The attempt count rides on the config, which axios copies onto the config
71
- // of the retried request, so nested attempts continue the same count and
72
- // separate requests never share one.
73
- config.__retryAttempt = attempt;
74
- // The body was serialised by the first pass; transforming it again would
75
- // JSON-encode the already-encoded string.
76
- config.transformRequest = [data => data];
77
- effective.onRetry(attempt, axiosError);
78
- const proceed = await waitOrAbort(effective.retryDelayMs(attempt), config.signal);
79
- if (!proceed) {
80
- return Promise.reject(error);
81
- }
82
- return client.request(config);
83
- });
84
- };
85
- //# sourceMappingURL=retry.js.map
1
+ export const isRetryableStatus=e=>"number"==typeof e&&(429===e||e>=500&&e<=599);const e=new Set(["get","head","options","put","delete"]),t={retries:3,retryDelayMs:e=>e<=1?0:2**e*1e3,shouldRetry:t=>{const r=t.response?.status;if(!isRetryableStatus(r))return!1;const s=(t.config?.method??"get").toLowerCase();return e.has(s)||429===r||503===r},onRetry:()=>{}};export const attachRetry=(e,r={})=>{e.interceptors.response.use(void 0,async s=>{const o=s,n=o?.config;if(!n||!1===n.retry)return Promise.reject(s);const i="object"==typeof n.retry?n.retry:{},a={...t,...r,...i},c=(n.__retryAttempt??0)+1;if(c>a.retries||!a.shouldRetry(o))return Promise.reject(s);if(n.signal?.aborted)return Promise.reject(s);n.__retryAttempt=c,n.transformRequest=[e=>e],a.onRetry(c,o);var y,u;return await(y=a.retryDelayMs(c),u=n.signal,new Promise(e=>{const onAbort=()=>{clearTimeout(t),e(!1)},t=setTimeout(()=>{u?.removeEventListener?.("abort",onAbort),e(!0)},y);u?.addEventListener?.("abort",onAbort,{once:!0})}))?e.request(n):Promise.reject(s)})};
@@ -1,192 +1 @@
1
- "use strict";
2
-
3
- import axios from 'axios';
4
- import { ACCESS_TOKEN_KEY, getToken, REFRESH_TOKEN_KEY, storeToken } from "../auth/authStore.js";
5
- import { Logger } from "../logger/index.js";
6
- import publicClient from "./publicClient.js";
7
-
8
- /**
9
- * The one token-refresh flow in the library, run by userClient's interceptors.
10
- * The library calls the refresh endpoint itself, so it can tell a rejected
11
- * refresh token (400 or 401: the session is over) from a transient failure
12
- * (network, timeout, 5xx: the session is kept and the caller sees the error).
13
- * A refresh is single flight. A download goes through the app's client, so it
14
- * reaches this only when that client is userClient.
15
- */
16
-
17
- /**
18
- * Minimal Redux-store shape required by the user client to dispatch logout.
19
- */
20
-
21
- let store;
22
- let auth;
23
- export const injectStore = _store => {
24
- store = _store;
25
- };
26
- export const injectUserAuth = options => {
27
- auth = options;
28
- };
29
-
30
- // Single promise to track token refresh
31
- let refreshTokenPromise = null;
32
-
33
- // Queue for failed requests
34
- let failedQueue = [];
35
-
36
- /**
37
- * Processes the queue of failed requests.
38
- */
39
- const processQueue = (error, token = null) => {
40
- failedQueue.forEach(({
41
- resolve,
42
- reject
43
- }) => {
44
- if (error) {
45
- reject(error);
46
- } else if (token !== null) {
47
- resolve(token);
48
- }
49
- });
50
- failedQueue = [];
51
- };
52
-
53
- /**
54
- * Clear any in-flight token refresh and reject everything queued behind it.
55
- * Call this on logout.
56
- *
57
- * Without it, a refresh that was already in flight when the user logged out
58
- * resolves afterwards and replays every queued request with a token for a
59
- * session that has ended. The queue is module state, so it survives the logout
60
- * entirely: nothing else clears it. A consumer app hit this and hand-wrote the
61
- * same function locally, which is how it was found.
62
- */
63
- export const resetRefreshState = () => {
64
- refreshTokenPromise = null;
65
- processQueue(new Error('[@webority/mobile-core] Session ended'));
66
- };
67
- export const getErrorMessage = error => {
68
- if (error instanceof Error) {
69
- return error.message;
70
- }
71
- if (typeof error === 'string') {
72
- return error;
73
- }
74
- return 'Unknown error';
75
- };
76
- const requireAuth = () => {
77
- if (!store || !auth) {
78
- throw new Error('User package store is not initialized. Call initUserAuth first.');
79
- }
80
- return {
81
- store,
82
- auth
83
- };
84
- };
85
-
86
- /** The refresh endpoint's path, once initUserAuth has run. */
87
- export const getRefreshPath = () => auth?.refresh.path;
88
- export const dispatchLogout = async () => {
89
- const wired = requireAuth();
90
- await wired.store.dispatch(wired.auth.logout());
91
- };
92
- export const isRefreshInFlight = () => refreshTokenPromise !== null;
93
-
94
- /** Wait for the in-flight refresh without joining it: resolved or rejected by processQueue. */
95
- export const queueBehindRefresh = () => new Promise((resolve, reject) => {
96
- failedQueue.push({
97
- resolve,
98
- reject
99
- });
100
- });
101
-
102
- /** A failure that proves the session is over, as opposed to one worth retrying later. */
103
- class SessionEndedError extends Error {
104
- constructor(message) {
105
- super(message);
106
- this.name = 'SessionEndedError';
107
- // Babel's class transform can drop the prototype link, which makes
108
- // `instanceof SessionEndedError` false in the transpiled build.
109
- Object.setPrototypeOf(this, SessionEndedError.prototype);
110
- }
111
- }
112
- const endsSession = error => error instanceof SessionEndedError || axios.isAxiosError(error) && (error.response?.status === 400 || error.response?.status === 401);
113
- const asString = (value, field) => {
114
- if (typeof value !== 'string' || value === '') {
115
- throw new Error(`The refresh response has no "${field}".`);
116
- }
117
- return value;
118
- };
119
-
120
- /** The Webority API's refresh response. */
121
- const readWeborityTokens = data => {
122
- const body = data ?? {};
123
- return {
124
- accessToken: asString(body.accessToken, 'accessToken'),
125
- accessTokenExpiry: asString(body.accessTokenExpiration, 'accessTokenExpiration'),
126
- refreshToken: asString(body.refreshToken, 'refreshToken'),
127
- refreshTokenExpiry: asString(body.refreshTokenExpiration, 'refreshTokenExpiration')
128
- };
129
- };
130
- const callRefreshEndpoint = async refresh => {
131
- const refreshToken = await getToken(REFRESH_TOKEN_KEY);
132
- if (!refreshToken?.token) {
133
- throw new SessionEndedError('No refresh token available');
134
- }
135
- // An expired refresh token would only earn a 401 from the endpoint before the same logout.
136
- if (Date.parse(refreshToken.expiry) <= Date.now()) {
137
- throw new SessionEndedError('Refresh token expired');
138
- }
139
- const accessToken = await getToken(ACCESS_TOKEN_KEY);
140
- const tokens = {
141
- accessToken: accessToken?.token ?? null,
142
- refreshToken: refreshToken.token
143
- };
144
- const body = refresh.body ? await refresh.body(tokens) : tokens;
145
- Logger.info('Refreshing the access token');
146
- const {
147
- data
148
- } = await publicClient.post(refresh.path, body);
149
- const next = (refresh.readTokens ?? readWeborityTokens)(data);
150
- await storeToken(ACCESS_TOKEN_KEY, next.accessToken, next.accessTokenExpiry);
151
- await storeToken(REFRESH_TOKEN_KEY, next.refreshToken, next.refreshTokenExpiry);
152
- Logger.info('Access token refreshed');
153
- return next.accessToken;
154
- };
155
-
156
- /**
157
- * Refresh the access token, joining a refresh already in flight rather than
158
- * starting a second one. Every request queued behind it is replayed on success
159
- * and rejected on failure. Logs out only when the failure proves the session
160
- * is over; a network error, timeout or 5xx keeps it and rejects.
161
- */
162
- export const refreshAccessToken = () => {
163
- if (refreshTokenPromise) {
164
- return refreshTokenPromise;
165
- }
166
- const run = async () => {
167
- try {
168
- const token = await callRefreshEndpoint(requireAuth().auth.refresh);
169
- processQueue(null, token);
170
- return token;
171
- } catch (error) {
172
- const responseData = axios.isAxiosError(error) ? error.response?.data : undefined;
173
- Logger.error('Token refresh failed:', responseData ?? getErrorMessage(error));
174
- processQueue(error, null);
175
- if (endsSession(error)) {
176
- await dispatchLogout();
177
- }
178
- throw error;
179
- }
180
- };
181
- // Cleared in a chained finally, not inside run: a failure before run's first
182
- // await would otherwise clear the slot before it was assigned, leaving a
183
- // rejected promise that every later refresh joins.
184
- const promise = run().finally(() => {
185
- if (refreshTokenPromise === promise) {
186
- refreshTokenPromise = null;
187
- }
188
- });
189
- refreshTokenPromise = promise;
190
- return promise;
191
- };
192
- //# sourceMappingURL=tokenRefresh.js.map
1
+ import e from"axios";import{ACCESS_TOKEN_KEY as r,getToken as o,REFRESH_TOKEN_KEY as s,storeToken as t}from"../auth/authStore.js";import{Logger as n}from"../logger/index.js";import i from"./publicClient.js";let a,c;export const injectStore=e=>{a=e};export const injectUserAuth=e=>{c=e};let h=null,p=[];const processQueue=(e,r=null)=>{p.forEach(({resolve:o,reject:s})=>{e?s(e):null!==r&&o(r)}),p=[]};export const resetRefreshState=()=>{h=null,processQueue(new Error("[@webority/mobile-core] Session ended"))};export const getErrorMessage=e=>e instanceof Error?e.message:"string"==typeof e?e:"Unknown error";const requireAuth=()=>{if(!a||!c)throw new Error("User package store is not initialized. Call initUserAuth first.");return{store:a,auth:c}};export const getRefreshPath=()=>c?.refresh.path;export const dispatchLogout=async()=>{const e=requireAuth();await e.store.dispatch(e.auth.logout())};export const isRefreshInFlight=()=>null!==h;export const queueBehindRefresh=()=>new Promise((e,r)=>{p.push({resolve:e,reject:r})});class SessionEndedError extends Error{constructor(e){super(e),this.name="SessionEndedError",Object.setPrototypeOf(this,SessionEndedError.prototype)}}const asString=(e,r)=>{if("string"!=typeof e||""===e)throw new Error(`The refresh response has no "${r}".`);return e},readWeborityTokens=e=>{const r=e??{};return{accessToken:asString(r.accessToken,"accessToken"),accessTokenExpiry:asString(r.accessTokenExpiration,"accessTokenExpiration"),refreshToken:asString(r.refreshToken,"refreshToken"),refreshTokenExpiry:asString(r.refreshTokenExpiration,"refreshTokenExpiration")}},callRefreshEndpoint=async e=>{const a=await o(s);if(!a?.token)throw new SessionEndedError("No refresh token available");if(Date.parse(a.expiry)<=Date.now())throw new SessionEndedError("Refresh token expired");const c=await o(r),h={accessToken:c?.token??null,refreshToken:a.token},p=e.body?await e.body(h):h;n.info("Refreshing the access token");const{data:f}=await i.post(e.path,p),l=(e.readTokens??readWeborityTokens)(f);return await t(r,l.accessToken,l.accessTokenExpiry),await t(s,l.refreshToken,l.refreshTokenExpiry),n.info("Access token refreshed"),l.accessToken};export const refreshAccessToken=()=>{if(h)return h;const r=(async()=>{try{const e=await callRefreshEndpoint(requireAuth().auth.refresh);return processQueue(null,e),e}catch(r){const o=e.isAxiosError(r)?r.response?.data:void 0;throw n.error("Token refresh failed:",o??getErrorMessage(r)),processQueue(r,null),(r=>r instanceof SessionEndedError||e.isAxiosError(r)&&(400===r.response?.status||401===r.response?.status))(r)&&await dispatchLogout(),r}})().finally(()=>{h===r&&(h=null)});return h=r,r};