@webority/mobile-core 0.0.29 → 0.2.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 (242) hide show
  1. package/README.md +32 -0
  2. package/lib/commonjs/api/baseUrl.js +1 -34
  3. package/lib/commonjs/api/clientHeaders.js +1 -0
  4. package/lib/commonjs/api/curl.js +1 -109
  5. package/lib/commonjs/api/minAppVersion.js +1 -0
  6. package/lib/commonjs/api/multipart.js +1 -21
  7. package/lib/commonjs/api/publicClient.js +1 -79
  8. package/lib/commonjs/api/requestDefaults.js +1 -0
  9. package/lib/commonjs/api/responseBodyText.js +1 -0
  10. package/lib/commonjs/api/retry.js +1 -91
  11. package/lib/commonjs/api/tokenRefresh.js +1 -205
  12. package/lib/commonjs/api/userClient.js +1 -265
  13. package/lib/commonjs/auth/authStore.js +1 -265
  14. package/lib/commonjs/auth/index.js +1 -44
  15. package/lib/commonjs/auth/jwt.js +1 -112
  16. package/lib/commonjs/config/index.js +1 -72
  17. package/lib/commonjs/deepLink/index.js +1 -286
  18. package/lib/commonjs/download/adapters/expoFs.js +1 -69
  19. package/lib/commonjs/download/adapters/shared.js +1 -13
  20. package/lib/commonjs/download/index.js +1 -250
  21. package/lib/commonjs/errors/extractApiError.js +1 -0
  22. package/lib/commonjs/errors/index.js +1 -0
  23. package/lib/commonjs/formatters/currency.js +1 -70
  24. package/lib/commonjs/formatters/date.js +1 -167
  25. package/lib/commonjs/formatters/dateMath.js +1 -311
  26. package/lib/commonjs/formatters/formatConfig.js +1 -0
  27. package/lib/commonjs/formatters/house.js +1 -0
  28. package/lib/commonjs/formatters/index.js +1 -246
  29. package/lib/commonjs/formatters/initials.js +1 -32
  30. package/lib/commonjs/formatters/intlCache.js +1 -37
  31. package/lib/commonjs/formatters/number.js +1 -89
  32. package/lib/commonjs/formatters/phone.js +1 -369
  33. package/lib/commonjs/formatters/windowsToIanaTable.js +1 -164
  34. package/lib/commonjs/geolocation/adapters/expoLocation.js +1 -126
  35. package/lib/commonjs/geolocation/index.js +1 -355
  36. package/lib/commonjs/index.js +1 -644
  37. package/lib/commonjs/initSDK.js +1 -66
  38. package/lib/commonjs/initUserAuth.js +1 -37
  39. package/lib/commonjs/logger/crashlytics.js +1 -0
  40. package/lib/commonjs/logger/index.js +1 -301
  41. package/lib/commonjs/logger/redact.js +1 -50
  42. package/lib/commonjs/network/index.js +1 -31
  43. package/lib/commonjs/network/networkStatus.js +1 -171
  44. package/lib/commonjs/permissions/calendar.js +1 -14
  45. package/lib/commonjs/permissions/camera.js +1 -14
  46. package/lib/commonjs/permissions/contacts.js +1 -10
  47. package/lib/commonjs/permissions/index.js +1 -133
  48. package/lib/commonjs/permissions/location.js +1 -14
  49. package/lib/commonjs/permissions/mediaLibrary.js +1 -10
  50. package/lib/commonjs/permissions/notifications.js +1 -10
  51. package/lib/commonjs/permissions/photos.js +1 -14
  52. package/lib/commonjs/phone/countries.js +1 -265
  53. package/lib/commonjs/push/index.js +1 -61
  54. package/lib/commonjs/push/push.js +1 -468
  55. package/lib/commonjs/push/status.js +1 -0
  56. package/lib/commonjs/push/task.js +1 -43
  57. package/lib/commonjs/query/apiBaseQuery.js +1 -0
  58. package/lib/commonjs/query/createAppApi.js +1 -0
  59. package/lib/commonjs/query/index.js +1 -0
  60. package/lib/commonjs/query/listeners.js +1 -0
  61. package/lib/commonjs/query/paging.js +1 -0
  62. package/lib/commonjs/query/tags.js +1 -0
  63. package/lib/commonjs/redux/errorToastMiddleware.js +1 -0
  64. package/lib/commonjs/redux/index.js +1 -0
  65. package/lib/commonjs/redux/rootReducer.js +1 -0
  66. package/lib/commonjs/redux/serializableCheck.js +1 -0
  67. package/lib/commonjs/secureScreen/index.js +1 -0
  68. package/lib/commonjs/share/index.js +1 -70
  69. package/lib/commonjs/smsRetriever/index.js +1 -91
  70. package/lib/commonjs/specs/NativeSmsRetriever.js +1 -15
  71. package/lib/commonjs/sqlite/adapters/expo.js +1 -53
  72. package/lib/commonjs/sqlite/index.js +1 -604
  73. package/lib/commonjs/storage/index.js +1 -214
  74. package/lib/commonjs/types/axios.js +1 -4
  75. package/lib/commonjs/types/globals.d.js +1 -6
  76. package/lib/commonjs/utils/base64.js +1 -24
  77. package/lib/commonjs/utils/imageCompression.js +1 -389
  78. package/lib/commonjs/utils/index.js +1 -19
  79. package/lib/commonjs/validators/index.js +1 -227
  80. package/lib/commonjs/versionCheck/buildNumber.js +1 -0
  81. package/lib/commonjs/versionCheck/index.js +1 -419
  82. package/lib/module/api/baseUrl.js +1 -30
  83. package/lib/module/api/clientHeaders.js +1 -0
  84. package/lib/module/api/curl.js +1 -104
  85. package/lib/module/api/minAppVersion.js +1 -0
  86. package/lib/module/api/multipart.js +1 -16
  87. package/lib/module/api/publicClient.js +1 -75
  88. package/lib/module/api/requestDefaults.js +1 -0
  89. package/lib/module/api/responseBodyText.js +1 -0
  90. package/lib/module/api/retry.js +1 -85
  91. package/lib/module/api/tokenRefresh.js +1 -192
  92. package/lib/module/api/userClient.js +1 -243
  93. package/lib/module/auth/authStore.js +1 -257
  94. package/lib/module/auth/index.js +1 -5
  95. package/lib/module/auth/jwt.js +1 -106
  96. package/lib/module/config/index.js +1 -64
  97. package/lib/module/deepLink/index.js +1 -280
  98. package/lib/module/download/adapters/expoFs.js +1 -64
  99. package/lib/module/download/adapters/shared.js +1 -8
  100. package/lib/module/download/index.js +1 -242
  101. package/lib/module/errors/extractApiError.js +1 -0
  102. package/lib/module/errors/index.js +1 -0
  103. package/lib/module/formatters/currency.js +1 -65
  104. package/lib/module/formatters/date.js +1 -159
  105. package/lib/module/formatters/dateMath.js +1 -295
  106. package/lib/module/formatters/formatConfig.js +1 -0
  107. package/lib/module/formatters/house.js +1 -0
  108. package/lib/module/formatters/index.js +1 -9
  109. package/lib/module/formatters/initials.js +1 -27
  110. package/lib/module/formatters/intlCache.js +1 -30
  111. package/lib/module/formatters/number.js +1 -82
  112. package/lib/module/formatters/phone.js +1 -302
  113. package/lib/module/formatters/windowsToIanaTable.js +1 -160
  114. package/lib/module/geolocation/adapters/expoLocation.js +1 -121
  115. package/lib/module/geolocation/index.js +1 -347
  116. package/lib/module/index.js +1 -57
  117. package/lib/module/initSDK.js +1 -61
  118. package/lib/module/initUserAuth.js +1 -33
  119. package/lib/module/logger/crashlytics.js +1 -0
  120. package/lib/module/logger/index.js +1 -295
  121. package/lib/module/logger/redact.js +1 -44
  122. package/lib/module/network/index.js +1 -4
  123. package/lib/module/network/networkStatus.js +1 -161
  124. package/lib/module/permissions/calendar.js +1 -15
  125. package/lib/module/permissions/camera.js +1 -15
  126. package/lib/module/permissions/contacts.js +1 -11
  127. package/lib/module/permissions/index.js +1 -128
  128. package/lib/module/permissions/location.js +1 -15
  129. package/lib/module/permissions/mediaLibrary.js +1 -11
  130. package/lib/module/permissions/notifications.js +1 -11
  131. package/lib/module/permissions/photos.js +1 -15
  132. package/lib/module/phone/countries.js +1 -261
  133. package/lib/module/push/index.js +1 -4
  134. package/lib/module/push/push.js +1 -452
  135. package/lib/module/push/status.js +1 -0
  136. package/lib/module/push/task.js +1 -38
  137. package/lib/module/query/apiBaseQuery.js +1 -0
  138. package/lib/module/query/createAppApi.js +1 -0
  139. package/lib/module/query/index.js +1 -0
  140. package/lib/module/query/listeners.js +1 -0
  141. package/lib/module/query/paging.js +1 -0
  142. package/lib/module/query/tags.js +1 -0
  143. package/lib/module/redux/errorToastMiddleware.js +1 -0
  144. package/lib/module/redux/index.js +1 -0
  145. package/lib/module/redux/rootReducer.js +1 -0
  146. package/lib/module/redux/serializableCheck.js +1 -0
  147. package/lib/module/secureScreen/index.js +1 -0
  148. package/lib/module/share/index.js +1 -64
  149. package/lib/module/smsRetriever/index.js +1 -86
  150. package/lib/module/specs/NativeSmsRetriever.js +1 -13
  151. package/lib/module/sqlite/adapters/expo.js +1 -48
  152. package/lib/module/sqlite/index.js +1 -594
  153. package/lib/module/storage/index.js +1 -208
  154. package/lib/module/types/axios.js +1 -4
  155. package/lib/module/types/globals.d.js +1 -10
  156. package/lib/module/utils/base64.js +1 -19
  157. package/lib/module/utils/imageCompression.js +1 -383
  158. package/lib/module/utils/index.js +1 -4
  159. package/lib/module/validators/index.js +1 -205
  160. package/lib/module/versionCheck/buildNumber.js +1 -0
  161. package/lib/module/versionCheck/index.js +1 -413
  162. package/lib/typescript/commonjs/api/clientHeaders.d.ts +25 -0
  163. package/lib/typescript/commonjs/api/minAppVersion.d.ts +24 -0
  164. package/lib/typescript/commonjs/api/requestDefaults.d.ts +21 -0
  165. package/lib/typescript/commonjs/api/responseBodyText.d.ts +6 -0
  166. package/lib/typescript/commonjs/api/retry.d.ts +23 -9
  167. package/lib/typescript/commonjs/api/tokenRefresh.d.ts +14 -2
  168. package/lib/typescript/commonjs/api/userClient.d.ts +1 -1
  169. package/lib/typescript/commonjs/auth/authStore.d.ts +76 -0
  170. package/lib/typescript/commonjs/auth/index.d.ts +2 -2
  171. package/lib/typescript/commonjs/config/index.d.ts +31 -2
  172. package/lib/typescript/commonjs/errors/extractApiError.d.ts +69 -0
  173. package/lib/typescript/commonjs/errors/index.d.ts +3 -0
  174. package/lib/typescript/commonjs/formatters/currency.d.ts +22 -7
  175. package/lib/typescript/commonjs/formatters/date.d.ts +13 -13
  176. package/lib/typescript/commonjs/formatters/dateMath.d.ts +4 -2
  177. package/lib/typescript/commonjs/formatters/formatConfig.d.ts +48 -0
  178. package/lib/typescript/commonjs/formatters/house.d.ts +143 -0
  179. package/lib/typescript/commonjs/formatters/index.d.ts +4 -0
  180. package/lib/typescript/commonjs/formatters/number.d.ts +15 -5
  181. package/lib/typescript/commonjs/index.d.ts +26 -8
  182. package/lib/typescript/commonjs/initSDK.d.ts +18 -6
  183. package/lib/typescript/commonjs/initUserAuth.d.ts +29 -0
  184. package/lib/typescript/commonjs/logger/crashlytics.d.ts +31 -0
  185. package/lib/typescript/commonjs/logger/index.d.ts +17 -0
  186. package/lib/typescript/commonjs/push/index.d.ts +3 -1
  187. package/lib/typescript/commonjs/push/push.d.ts +1 -1
  188. package/lib/typescript/commonjs/push/status.d.ts +9 -0
  189. package/lib/typescript/commonjs/query/apiBaseQuery.d.ts +55 -0
  190. package/lib/typescript/commonjs/query/createAppApi.d.ts +21 -0
  191. package/lib/typescript/commonjs/query/index.d.ts +9 -0
  192. package/lib/typescript/commonjs/query/listeners.d.ts +11 -0
  193. package/lib/typescript/commonjs/query/paging.d.ts +42 -0
  194. package/lib/typescript/commonjs/query/tags.d.ts +31 -0
  195. package/lib/typescript/commonjs/redux/errorToastMiddleware.d.ts +51 -0
  196. package/lib/typescript/commonjs/redux/index.d.ts +6 -0
  197. package/lib/typescript/commonjs/redux/rootReducer.d.ts +19 -0
  198. package/lib/typescript/commonjs/redux/serializableCheck.d.ts +14 -0
  199. package/lib/typescript/commonjs/secureScreen/index.d.ts +25 -0
  200. package/lib/typescript/commonjs/versionCheck/buildNumber.d.ts +14 -0
  201. package/lib/typescript/commonjs/versionCheck/index.d.ts +28 -17
  202. package/lib/typescript/module/api/clientHeaders.d.ts +25 -0
  203. package/lib/typescript/module/api/minAppVersion.d.ts +24 -0
  204. package/lib/typescript/module/api/requestDefaults.d.ts +21 -0
  205. package/lib/typescript/module/api/responseBodyText.d.ts +6 -0
  206. package/lib/typescript/module/api/retry.d.ts +23 -9
  207. package/lib/typescript/module/api/tokenRefresh.d.ts +14 -2
  208. package/lib/typescript/module/api/userClient.d.ts +1 -1
  209. package/lib/typescript/module/auth/authStore.d.ts +76 -0
  210. package/lib/typescript/module/auth/index.d.ts +2 -2
  211. package/lib/typescript/module/config/index.d.ts +31 -2
  212. package/lib/typescript/module/errors/extractApiError.d.ts +69 -0
  213. package/lib/typescript/module/errors/index.d.ts +3 -0
  214. package/lib/typescript/module/formatters/currency.d.ts +22 -7
  215. package/lib/typescript/module/formatters/date.d.ts +13 -13
  216. package/lib/typescript/module/formatters/dateMath.d.ts +4 -2
  217. package/lib/typescript/module/formatters/formatConfig.d.ts +48 -0
  218. package/lib/typescript/module/formatters/house.d.ts +143 -0
  219. package/lib/typescript/module/formatters/index.d.ts +4 -0
  220. package/lib/typescript/module/formatters/number.d.ts +15 -5
  221. package/lib/typescript/module/index.d.ts +26 -8
  222. package/lib/typescript/module/initSDK.d.ts +18 -6
  223. package/lib/typescript/module/initUserAuth.d.ts +29 -0
  224. package/lib/typescript/module/logger/crashlytics.d.ts +31 -0
  225. package/lib/typescript/module/logger/index.d.ts +17 -0
  226. package/lib/typescript/module/push/index.d.ts +3 -1
  227. package/lib/typescript/module/push/push.d.ts +1 -1
  228. package/lib/typescript/module/push/status.d.ts +9 -0
  229. package/lib/typescript/module/query/apiBaseQuery.d.ts +55 -0
  230. package/lib/typescript/module/query/createAppApi.d.ts +21 -0
  231. package/lib/typescript/module/query/index.d.ts +9 -0
  232. package/lib/typescript/module/query/listeners.d.ts +11 -0
  233. package/lib/typescript/module/query/paging.d.ts +42 -0
  234. package/lib/typescript/module/query/tags.d.ts +31 -0
  235. package/lib/typescript/module/redux/errorToastMiddleware.d.ts +51 -0
  236. package/lib/typescript/module/redux/index.d.ts +6 -0
  237. package/lib/typescript/module/redux/rootReducer.d.ts +19 -0
  238. package/lib/typescript/module/redux/serializableCheck.d.ts +14 -0
  239. package/lib/typescript/module/secureScreen/index.d.ts +25 -0
  240. package/lib/typescript/module/versionCheck/buildNumber.d.ts +14 -0
  241. package/lib/typescript/module/versionCheck/index.d.ts +28 -17
  242. package/package.json +52 -5
package/README.md CHANGED
@@ -32,11 +32,43 @@ import { shareFile } from '@webority/mobile-core/share';
32
32
  import { initPush, enablePush, usePush } from '@webority/mobile-core/push';
33
33
  import { definePushActionTask } from '@webority/mobile-core/push/task';
34
34
  import { compressImage } from '@webority/mobile-core/compressor';
35
+ import { createApiBaseQuery, createAppApi, setupApiListeners } from '@webority/mobile-core/query';
35
36
  ```
36
37
 
37
38
  Metro resolves requires statically, so anything reachable from the main barrel is bundled for
38
39
  every consumer. Keeping these off it is what makes their peers genuinely optional.
39
40
 
41
+ `/query` is the RTK Query layer; it needs `@reduxjs/toolkit` (2.6 or later) and `react-redux`,
42
+ both optional peers. One `createApi` per app, built with `createAppApi` and the base query over
43
+ the library's clients, so refresh, retry, the offline check and the `extractApiError` failure
44
+ shape stay in one place. Never a `fetchBaseQuery`.
45
+
46
+ ```ts
47
+ // services/api/apiSlice.ts
48
+ import { publicClient, userClient } from '@webority/mobile-core';
49
+ import { createApiBaseQuery, createAppApi, buildListQuery, providesPagedList,
50
+ type PagedDataObject } from '@webority/mobile-core/query';
51
+
52
+ export const apiSlice = createAppApi({
53
+ reducerPath: 'api',
54
+ baseQuery: createApiBaseQuery({ client: userClient, publicClient }),
55
+ tagTypes: ['Project'],
56
+ endpoints: (build) => ({
57
+ getProjects: build.query<PagedDataObject<Project>, { page: number }>({
58
+ query: ({ page }) => ({ url: '/api/projects', params: buildListQuery({ page, pageSize: 20 }) }),
59
+ providesTags: providesPagedList('Project')
60
+ })
61
+ })
62
+ });
63
+ // store.ts: add apiSlice.reducer and apiSlice.middleware, then setupApiListeners(store.dispatch)
64
+ ```
65
+
66
+ `setupApiListeners` maps the app returning to the foreground (`AppState`) to RTK Query's focus,
67
+ and this library's network status to online and offline; RTK's own defaults listen to `window`
68
+ events that never fire on a device. A call marked `silent: true`, or an endpoint with
69
+ `extraOptions: { silent: true }`, carries `silent` on `action.meta.baseQueryMeta`, which the
70
+ error toast middleware skips.
71
+
40
72
  `/push` registers the phone for push notifications. It needs `expo-notifications`, an optional
41
73
  peer, and reports `unavailable` without it or in Expo Go. Call `initPush` once at startup with
42
74
  `getInstallationId`, `register`, `unregister` and `onOpen`; `enablePush` asks permission, sends
@@ -1,34 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.applyConfiguredBaseUrl = void 0;
7
- var _index = require("../config/index.js");
8
- /**
9
- * Resolves `baseURL` PER REQUEST rather than at module evaluation.
10
- *
11
- * `axios.create({ baseURL: getConfigValue('baseUrl') })` reads the value once,
12
- * when the module is first imported — which happens the moment anything touches
13
- * the package barrel, long before an app's startup code can call `setConfig`.
14
- * The result was a client permanently pinned to `undefined` and a `setConfig`
15
- * that appeared to do nothing, with no error anywhere: requests just went to a
16
- * relative URL.
17
- *
18
- * A consumer cannot fix that by ordering its own imports, because the import
19
- * that loses the race is usually one it never wrote. So the clients stop
20
- * capturing the value and ask for it per request instead.
21
- *
22
- * An explicit per-request `baseURL` still wins, which is what keeps a one-off
23
- * call to a different host possible.
24
- */
25
- const applyConfiguredBaseUrl = client => {
26
- client.interceptors.request.use(config => {
27
- if (!config.baseURL) {
28
- config.baseURL = (0, _index.getConfigValue)('baseUrl') ?? undefined;
29
- }
30
- return config;
31
- });
32
- };
33
- exports.applyConfiguredBaseUrl = applyConfiguredBaseUrl;
34
- //# sourceMappingURL=baseUrl.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.applyConfiguredBaseUrl=void 0;var _index=require("../config/index.js");const applyConfiguredBaseUrl=e=>{e.interceptors.request.use(e=>(e.baseURL||(e.baseURL=(0,_index.getConfigValue)("baseUrl")??void 0),e))};exports.applyConfiguredBaseUrl=applyConfiguredBaseUrl;
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.getClientBuild=exports.applyClientHeaders=exports.applyAccountIdHeader=exports.CLIENT_KIND_HEADER=exports.CLIENT_KIND=exports.CLIENT_BUILD_HEADER=exports.ACCOUNT_ID_HEADER=void 0;var _index=require("../config/index.js");const CLIENT_BUILD_HEADER=exports.CLIENT_BUILD_HEADER="X-Client-Build",CLIENT_KIND_HEADER=exports.CLIENT_KIND_HEADER="X-Client-Kind",ACCOUNT_ID_HEADER=exports.ACCOUNT_ID_HEADER="X-Account-Id",CLIENT_KIND=exports.CLIENT_KIND="MobileApp",getClientBuild=()=>{const e=(0,_index.getConfigValue)("appVersion");if(!e)return null;const t=(0,_index.getConfigValue)("commit");return t?`${e}+${t}`:e};exports.getClientBuild=getClientBuild;const setIfAbsent=(e,t,s)=>{s&&!e.has(t)&&e.set(t,s)},applyClientHeaders=e=>{e.interceptors.request.use(e=>(setIfAbsent(e.headers,CLIENT_KIND_HEADER,CLIENT_KIND),setIfAbsent(e.headers,CLIENT_BUILD_HEADER,getClientBuild()),e))};exports.applyClientHeaders=applyClientHeaders;const applyAccountIdHeader=async(e,t)=>{const s=t?await t():null;s&&e.set(ACCOUNT_ID_HEADER,s)};exports.applyAccountIdHeader=applyAccountIdHeader;
@@ -1,109 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.logCurl = exports.buildCurl = void 0;
7
- var _index = require("../logger/index.js");
8
- /**
9
- * Header names whose VALUE is a credential. Matched case-insensitively against
10
- * the whole name, since `Authorization` and `authorization` are the same header
11
- * and axios does not normalise the case of headers a caller sets by hand.
12
- */
13
- 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']);
14
-
15
- /**
16
- * Body fields whose value is a credential. Matched on the key alone, so this
17
- * catches `password`, `newPassword` and `password_confirmation` alike.
18
- */
19
- const SECRET_BODY_PATTERN = /pass(word|code)|secret|token|otp|pin|cvv|authoriz/i;
20
-
21
- /** The axios per-method header buckets, which are config plumbing, not headers. */
22
- const AXIOS_METHOD_KEYS = new Set(['common', 'delete', 'get', 'head', 'post', 'put', 'patch']);
23
- const REDACTED = '<redacted>';
24
- const redactBody = (value, seen, depth = 0) => {
25
- if (value === null || typeof value !== 'object') {
26
- return value;
27
- }
28
- // Both guards are needed and they catch different shapes. `seen` breaks a
29
- // true cycle, which would otherwise recurse forever AND make the eventual
30
- // JSON.stringify throw. The depth bound catches a body that is merely
31
- // enormous. A curl line is a debugging aid, and it must never be able to
32
- // hang or fail the request it is describing.
33
- if (seen.has(value)) {
34
- return '<circular>';
35
- }
36
- if (depth > 6) {
37
- return '<truncated>';
38
- }
39
- seen.add(value);
40
- if (Array.isArray(value)) {
41
- return value.map(entry => redactBody(entry, seen, depth + 1));
42
- }
43
- const out = {};
44
- for (const [key, entry] of Object.entries(value)) {
45
- out[key] = SECRET_BODY_PATTERN.test(key) ? REDACTED : redactBody(entry, seen, depth + 1);
46
- }
47
- return out;
48
- };
49
- const buildUrl = config => {
50
- const url = config.url ?? '';
51
- const base = config.baseURL ? config.baseURL.replace(/\/$/, '') : '';
52
- let complete = url.startsWith('http') ? url : `${base}/${url.replace(/^\//, '')}`;
53
- if (config.params) {
54
- const query = new URLSearchParams(config.params).toString();
55
- if (query) {
56
- complete += (complete.includes('?') ? '&' : '?') + query;
57
- }
58
- }
59
- return complete;
60
- };
61
-
62
- /**
63
- * Builds a copy-pasteable curl command for a request, with every credential
64
- * replaced by a placeholder.
65
- *
66
- * The redaction is the point, not a nicety. An unredacted version of this in a
67
- * consumer app put `Authorization: Bearer <token>` and the login request's
68
- * plaintext password into the device console of a *release* staging build.
69
- * A curl line is worth having; a curl line that has to be trusted not to be
70
- * enabled in the wrong build is not.
71
- */
72
- const buildCurl = config => {
73
- const method = (config.method ?? 'get').toUpperCase();
74
- let command = `curl -X ${method} "${buildUrl(config)}"`;
75
- const headers = config.headers;
76
- if (headers) {
77
- for (const [key, value] of Object.entries(headers)) {
78
- if (AXIOS_METHOD_KEYS.has(key)) {
79
- continue;
80
- }
81
- if (typeof value !== 'string' && typeof value !== 'number') {
82
- continue;
83
- }
84
- const shown = SECRET_HEADERS.has(key.toLowerCase()) ? REDACTED : value;
85
- command += ` -H "${key}: ${shown}"`;
86
- }
87
- }
88
- if (config.data !== undefined && config.data !== null) {
89
- const redacted = typeof config.data === 'object' ? redactBody(config.data, new WeakSet()) : config.data;
90
- const body = typeof redacted === 'object' ? JSON.stringify(redacted) : String(redacted);
91
- command += ` -d '${body.replace(/'/g, "'\\''")}'`;
92
- }
93
- return command;
94
- };
95
-
96
- /**
97
- * Logs the curl equivalent of a request. Never throws: a failure to describe a
98
- * request must not be able to fail the request itself.
99
- */
100
- exports.buildCurl = buildCurl;
101
- const logCurl = config => {
102
- try {
103
- _index.Logger.info('CURL Command:', buildCurl(config));
104
- } catch (error) {
105
- _index.Logger.warn('[api] Failed to build a curl command for the request', error);
106
- }
107
- };
108
- exports.logCurl = logCurl;
109
- //# sourceMappingURL=curl.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.logCurl=exports.buildCurl=void 0;var _index=require("../logger/index.js");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"]),SECRET_BODY_PATTERN=/pass(word|code)|secret|token|otp|pin|cvv|authoriz/i,AXIOS_METHOD_KEYS=new Set(["common","delete","get","head","post","put","patch"]),REDACTED="<redacted>",redactBody=(e,t,r=0)=>{if(null===e||"object"!=typeof e)return e;if(t.has(e))return"<circular>";if(r>6)return"<truncated>";if(t.add(e),Array.isArray(e))return e.map(e=>redactBody(e,t,r+1));const o={};for(const[a,i]of Object.entries(e))o[a]=SECRET_BODY_PATTERN.test(a)?REDACTED:redactBody(i,t,r+1);return o},buildUrl=e=>{const t=e.url??"",r=e.baseURL?e.baseURL.replace(/\/$/,""):"";let o=t.startsWith("http")?t:`${r}/${t.replace(/^\//,"")}`;if(e.params){const t=new URLSearchParams(e.params).toString();t&&(o+=(o.includes("?")?"&":"?")+t)}return o},buildCurl=e=>{let t=`curl -X ${(e.method??"get").toUpperCase()} "${buildUrl(e)}"`;const r=e.headers;if(r)for(const[e,o]of Object.entries(r)){if(AXIOS_METHOD_KEYS.has(e))continue;if("string"!=typeof o&&"number"!=typeof o)continue;const r=SECRET_HEADERS.has(e.toLowerCase())?REDACTED:o;t+=` -H "${e}: ${r}"`}if(void 0!==e.data&&null!==e.data){const r="object"==typeof e.data?redactBody(e.data,new WeakSet):e.data;t+=` -d '${("object"==typeof r?JSON.stringify(r):String(r)).replace(/'/g,"'\\''")}'`}return t};exports.buildCurl=buildCurl;const logCurl=e=>{try{_index.Logger.info("CURL Command:",buildCurl(e))}catch(e){_index.Logger.warn("[api] Failed to build a curl command for the request",e)}};exports.logCurl=logCurl;
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.readMinAppVersion=exports.getMinAppVersion=exports.addMinAppVersionListener=exports.MIN_APP_VERSION_HEADER=void 0;var _index=require("../logger/index.js");const MIN_APP_VERSION_HEADER=exports.MIN_APP_VERSION_HEADER="X-Min-App-Version";let current=null;const listeners=new Set,getMinAppVersion=()=>current;exports.getMinAppVersion=getMinAppVersion;const addMinAppVersionListener=e=>(listeners.add(e),null!==current&&e(current),()=>{listeners.delete(e)});exports.addMinAppVersionListener=addMinAppVersionListener;const readHeader=e=>{if(!e||"object"!=typeof e)return;const r=e.get;return"function"==typeof r?r.call(e,MIN_APP_VERSION_HEADER):e[MIN_APP_VERSION_HEADER.toLowerCase()]},record=e=>{const r=readHeader(e?.headers);if(null==r||""===r)return;const n=String(r).trim();if(!/^\d+$/.test(n))return void _index.Logger.warn(`[minAppVersion] ignored a non-integer ${MIN_APP_VERSION_HEADER}: ${n}`);const t=Number.parseInt(n,10);if(t!==current){current=t;for(const e of[...listeners])try{e(t)}catch(e){_index.Logger.error("[minAppVersion] a listener threw",e)}}},readMinAppVersion=e=>{e.interceptors.response.use(e=>(record(e),e),e=>(record(e?.response),Promise.reject(e)))};exports.readMinAppVersion=readMinAppVersion;
@@ -1,21 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.sendFormDataAsMultipart = void 0;
7
- /**
8
- * The clients default to a JSON content type, and axios then serialises a FormData body to
9
- * JSON, which breaks every upload. Dropping the header for FormData lets the platform write
10
- * the multipart header with its boundary.
11
- */
12
- const sendFormDataAsMultipart = client => {
13
- client.interceptors.request.use(config => {
14
- if (typeof FormData !== 'undefined' && config.data instanceof FormData) {
15
- config.headers?.delete?.('Content-Type');
16
- }
17
- return config;
18
- });
19
- };
20
- exports.sendFormDataAsMultipart = sendFormDataAsMultipart;
21
- //# sourceMappingURL=multipart.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.sendFormDataAsMultipart=void 0;const sendFormDataAsMultipart=e=>{e.interceptors.request.use(e=>("undefined"!=typeof FormData&&e.data instanceof FormData&&e.headers?.delete?.("Content-Type"),e))};exports.sendFormDataAsMultipart=sendFormDataAsMultipart;
@@ -1,79 +1 @@
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 _index = require("../config/index.js");
9
- var _index2 = require("../logger/index.js");
10
- require("../types/axios.js");
11
- var _baseUrl = require("./baseUrl.js");
12
- var _curl = require("./curl.js");
13
- var _multipart = require("./multipart.js");
14
- var _retry = require("./retry.js");
15
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
16
- // Define timeout for requests
17
- const REQUEST_TIMEOUT = 30000; // 30 seconds
18
-
19
- /**
20
- * Public Axios Client
21
- * - Base URL: Set your public API base URL.
22
- * - No Authorization header is attached.
23
- * - Includes timeout configuration and error handling.
24
- * - Handles specific HTTP status codes.
25
- */
26
- const publicClient = _axios.default.create({
27
- headers: {
28
- 'Content-Type': 'application/json',
29
- Accept: 'application/json',
30
- 'Accept-Encoding': 'gzip, deflate, br'
31
- },
32
- timeout: REQUEST_TIMEOUT
33
- });
34
- (0, _baseUrl.applyConfiguredBaseUrl)(publicClient);
35
- (0, _multipart.sendFormDataAsMultipart)(publicClient);
36
-
37
- // Registered before the logging interceptors below so they only ever observe
38
- // the final outcome of a request, not its intermediate retries. The policy
39
- // itself, and why a bare network error is never retried, lives in ./retry.
40
- (0, _retry.attachRetry)(publicClient);
41
-
42
- // Add request interceptor for logging
43
- publicClient.interceptors.request.use(config => {
44
- _index2.Logger.info(`Request: ${config.method?.toUpperCase()} ${config.url}`);
45
- if ((0, _index.getConfigValue)('logCurl')) {
46
- (0, _curl.logCurl)(config);
47
- }
48
- return config;
49
- }, error => {
50
- const message = error instanceof Error ? error.message : String(error);
51
- _index2.Logger.error('Request error:', message);
52
- return Promise.reject(error);
53
- });
54
-
55
- // Add response interceptor for error handling
56
- publicClient.interceptors.response.use(response => {
57
- _index2.Logger.info(`Response: ${response.status} from ${response.config.url}`);
58
- return response;
59
- }, error => {
60
- if (_axios.default.isAxiosError(error)) {
61
- const originalRequest = error.config;
62
- if (originalRequest?.skipErrorLog) {
63
- return Promise.reject(error);
64
- }
65
- if (!error.response) {
66
- _index2.Logger.error(`Network error or no response from server: ${error.message}, URL: ${originalRequest?.url}`);
67
- } else {
68
- const body = error.response.data;
69
- const bodyText = body instanceof ArrayBuffer ? `binary response (${body.byteLength} bytes)` : typeof Blob !== 'undefined' && body instanceof Blob ? `binary response (${body.size} bytes)` : body ?? error.message;
70
- _index2.Logger.error(`Response error: ${bodyText}, URL: ${originalRequest?.url}, Status: ${error.response.status}`);
71
- }
72
- } else {
73
- const message = error instanceof Error ? error.message : String(error);
74
- _index2.Logger.error(`Unknown response error: ${message}`);
75
- }
76
- return Promise.reject(error);
77
- });
78
- var _default = exports.default = publicClient;
79
- //# sourceMappingURL=publicClient.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=void 0;var _axios=_interopRequireDefault(require("axios")),_index=require("../config/index.js"),_index2=require("../logger/index.js");require("../types/axios.js");var _baseUrl=require("./baseUrl.js"),_clientHeaders=require("./clientHeaders.js"),_curl=require("./curl.js"),_minAppVersion=require("./minAppVersion.js"),_multipart=require("./multipart.js"),_requestDefaults=require("./requestDefaults.js"),_responseBodyText=require("./responseBodyText.js"),_retry=require("./retry.js");function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}const publicClient=_axios.default.create({headers:{"Content-Type":"application/json",Accept:"application/json"}});(0,_baseUrl.applyConfiguredBaseUrl)(publicClient),(0,_requestDefaults.applyRequestDefaults)(publicClient),(0,_multipart.sendFormDataAsMultipart)(publicClient),(0,_minAppVersion.readMinAppVersion)(publicClient),(0,_retry.attachRetry)(publicClient),publicClient.interceptors.request.use(e=>(_index2.Logger.info(`Request: ${e.method?.toUpperCase()} ${e.url}`),(0,_index.getConfigValue)("logCurl")&&(0,_curl.logCurl)(e),e),e=>{const r=e instanceof Error?e.message:String(e);return _index2.Logger.error("Request error:",r),Promise.reject(e)}),(0,_clientHeaders.applyClientHeaders)(publicClient),publicClient.interceptors.response.use(e=>(_index2.Logger.info(`Response: ${e.status} from ${e.config.url}`),e),e=>{if(_axios.default.isAxiosError(e)){const r=e.config;if(r?.skipErrorLog)return Promise.reject(e);e.response?_index2.Logger.error(`Response error: ${(0,_responseBodyText.responseBodyText)(e.response.data,e.message)}, URL: ${r?.url}, Status: ${e.response.status}`):_index2.Logger.error(`Network error or no response from server: ${e.message}, URL: ${r?.url}`)}else{const r=e instanceof Error?e.message:String(e);_index2.Logger.error(`Unknown response error: ${r}`)}return Promise.reject(e)});var _default=exports.default=publicClient;
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.newCorrelationId=exports.applyRequestDefaults=void 0;var _index=require("../config/index.js");const newCorrelationId=()=>{const e=globalThis.crypto;if("function"==typeof e?.randomUUID)return e.randomUUID();const t=new Uint8Array(16);if("function"==typeof e?.getRandomValues)e.getRandomValues(t);else for(let e=0;e<t.length;e+=1)t[e]=Math.floor(256*Math.random());t[6]=15&(t[6]??0)|64,t[8]=63&(t[8]??0)|128;const o=Array.from(t,e=>e.toString(16).padStart(2,"0")).join("");return`${o.slice(0,8)}-${o.slice(8,12)}-${o.slice(12,16)}-${o.slice(16,20)}-${o.slice(20)}`};exports.newCorrelationId=newCorrelationId;const applyRequestDefaults=e=>{e.defaults.timeout=void 0,e.interceptors.request.use(e=>{void 0===e.timeout&&(e.timeout=(0,_index.getConfigValue)("timeoutMs"));const t=(0,_index.getConfigValue)("correlationIdHeader");return t&&!e.headers.has(t)&&e.headers.set(t,newCorrelationId()),e})};exports.applyRequestDefaults=applyRequestDefaults;
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.responseBodyText=void 0;const MAX_LENGTH=500,responseBodyText=(e,s)=>{if(null==e||""===e)return s;if(e instanceof ArrayBuffer)return`binary response (${e.byteLength} bytes)`;if("undefined"!=typeof Blob&&e instanceof Blob)return`binary response (${e.size} bytes)`;const t="string"==typeof e?e:JSON.stringify(e);return t.length>500?`${t.slice(0,500)}...`:t};exports.responseBodyText=responseBodyText;
@@ -1,91 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.isRetryableStatus = exports.attachRetry = void 0;
7
- /**
8
- * Status-based ONLY, deliberately: a bare network error (no `error.response`)
9
- * is never retryable. Both clients carry non-idempotent requests, login and OTP
10
- * on the public one and every write on the user one, and a broken connection
11
- * says nothing about whether the request reached the server. Retrying can
12
- * therefore create a second session, or a duplicate record, that the user never
13
- * sees. A generic network-error retry rule is not a substitute: it decides on
14
- * the transport alone and never looks at the HTTP method, so it retries POSTs
15
- * as readily as GETs.
16
- */
17
- const isRetryableStatus = status => typeof status === 'number' && (status === 429 || status >= 500 && status <= 599);
18
- exports.isRetryableStatus = isRetryableStatus;
19
- const IDEMPOTENT_METHODS = new Set(['get', 'head', 'options', 'put', 'delete']);
20
-
21
- /**
22
- * The same reasoning reaches status codes on a write. A 500 on a POST or PATCH
23
- * can follow a partial commit, so replaying it can duplicate the record; only
24
- * 429 and 503 say the server refused the request before doing the work. A
25
- * request that knows its write is safe to repeat passes its own `shouldRetry`.
26
- */
27
- const isRetryableFailure = error => {
28
- const status = error.response?.status;
29
- if (!isRetryableStatus(status)) {
30
- return false;
31
- }
32
- const method = (error.config?.method ?? 'get').toLowerCase();
33
- return IDEMPOTENT_METHODS.has(method) || status === 429 || status === 503;
34
- };
35
- const DEFAULT_POLICY = {
36
- retries: 3,
37
- retryDelayMs: attempt => attempt <= 1 ? 0 : 2 ** attempt * 1000,
38
- shouldRetry: isRetryableFailure,
39
- onRetry: () => undefined
40
- };
41
- const waitOrAbort = (delay, signal) => new Promise(resolve => {
42
- const onAbort = () => {
43
- clearTimeout(timer);
44
- resolve(false);
45
- };
46
- const timer = setTimeout(() => {
47
- signal?.removeEventListener?.('abort', onAbort);
48
- resolve(true);
49
- }, delay);
50
- signal?.addEventListener?.('abort', onAbort, {
51
- once: true
52
- });
53
- });
54
- const attachRetry = (client, policy = {}) => {
55
- client.interceptors.response.use(undefined, async error => {
56
- const axiosError = error;
57
- const config = axiosError?.config;
58
- if (!config || config.retry === false) {
59
- return Promise.reject(error);
60
- }
61
- const perRequest = typeof config.retry === 'object' ? config.retry : {};
62
- const effective = {
63
- ...DEFAULT_POLICY,
64
- ...policy,
65
- ...perRequest
66
- };
67
- const attempt = (config.__retryAttempt ?? 0) + 1;
68
- if (attempt > effective.retries || !effective.shouldRetry(axiosError)) {
69
- return Promise.reject(error);
70
- }
71
- if (config.signal?.aborted) {
72
- return Promise.reject(error);
73
- }
74
-
75
- // The attempt count rides on the config, which axios copies onto the config
76
- // of the retried request, so nested attempts continue the same count and
77
- // separate requests never share one.
78
- config.__retryAttempt = attempt;
79
- // The body was serialised by the first pass; transforming it again would
80
- // JSON-encode the already-encoded string.
81
- config.transformRequest = [data => data];
82
- effective.onRetry(attempt, axiosError);
83
- const proceed = await waitOrAbort(effective.retryDelayMs(attempt), config.signal);
84
- if (!proceed) {
85
- return Promise.reject(error);
86
- }
87
- return client.request(config);
88
- });
89
- };
90
- exports.attachRetry = attachRetry;
91
- //# sourceMappingURL=retry.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.retryAfterMs=exports.isRetryableStatus=exports.isRetryableFailure=exports.isNeverSentError=exports.attachRetry=exports.IDEMPOTENCY_KEY_HEADER=void 0;var _axios=_interopRequireDefault(require("axios")),_index=require("../config/index.js");function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}const IDEMPOTENCY_KEY_HEADER=exports.IDEMPOTENCY_KEY_HEADER="Idempotency-Key",isRetryableStatus=e=>"number"==typeof e&&(429===e||e>=500&&e<=599);exports.isRetryableStatus=isRetryableStatus;const IDEMPOTENT_METHODS=new Set(["get","head","options","put","delete"]),NEVER_SENT_CODES=new Set(["ECONNREFUSED","ENOTFOUND","EAI_AGAIN","ENETUNREACH","EHOSTUNREACH"]),isNeverSentError=e=>!e.response&&"string"==typeof e.code&&NEVER_SENT_CODES.has(e.code);exports.isNeverSentError=isNeverSentError;const MAX_RETRY_AFTER_MS=3e4,retryAfterMs=e=>{const t=e.response;if(!t)return null;const r=t.data,s=t.headers,o="function"==typeof s?.get?s.get("retry-after"):s?.["retry-after"];for(const e of[r?.retryAfterSeconds,o]){const t=Number(e);if(null!=e&&Number.isFinite(t)&&t>0)return 1e3*t}return null};exports.retryAfterMs=retryAfterMs;const hasIdempotencyKey=e=>{const t=e?.headers,r="function"==typeof t?.get?t.get(IDEMPOTENCY_KEY_HEADER):t?.[IDEMPOTENCY_KEY_HEADER];return"string"==typeof r&&""!==r},isRetryableFailure=e=>{if(_axios.default.isCancel(e)||"ERR_CANCELED"===e.code)return!1;if(isNeverSentError(e))return!0;const t=(e.config?.method??"get").toLowerCase(),r=IDEMPOTENT_METHODS.has(t),s=e.response?.status;if(void 0===s)return r;if(!isRetryableStatus(s))return!1;const o=retryAfterMs(e);return!(null!==o&&o>3e4)&&(r||(429===s||503===s)&&hasIdempotencyKey(e.config))};exports.isRetryableFailure=isRetryableFailure;const DEFAULT_POLICY={retryDelayMs:e=>e<=1?0:2**e*1e3,shouldRetry:isRetryableFailure,onRetry:()=>{}},waitOrAbort=(e,t)=>new Promise(r=>{const onAbort=()=>{clearTimeout(s),r(!1)},s=setTimeout(()=>{t?.removeEventListener?.("abort",onAbort),r(!0)},e);t?.addEventListener?.("abort",onAbort,{once:!0})}),attachRetry=(e,t={})=>{e.interceptors.response.use(void 0,async r=>{const s=r,o=s?.config;if(!o||!1===o.retry)return Promise.reject(r);const n="object"==typeof o.retry?o.retry:{},i={...DEFAULT_POLICY,retries:(0,_index.getConfigValue)("retries"),...t,...n},a=(o.__retryAttempt??0)+1;if(a>i.retries||!i.shouldRetry(s))return Promise.reject(r);if(o.signal?.aborted)return Promise.reject(r);o.__retryAttempt=a,o.transformRequest=[e=>e],i.onRetry(a,s);const E=retryAfterMs(s)??i.retryDelayMs(a);return await waitOrAbort(E,o.signal)?e.request(o):Promise.reject(r)})};exports.attachRetry=attachRetry;
@@ -1,205 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.resetRefreshState = exports.refreshAccessToken = exports.queueBehindRefresh = exports.isRefreshInFlight = exports.injectUserAuth = exports.injectStore = exports.getRefreshPath = exports.getErrorMessage = exports.dispatchLogout = void 0;
7
- var _axios = _interopRequireDefault(require("axios"));
8
- var _authStore = require("../auth/authStore.js");
9
- var _index = require("../logger/index.js");
10
- var _publicClient = _interopRequireDefault(require("./publicClient.js"));
11
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
12
- /**
13
- * The one token-refresh flow in the library, run by userClient's interceptors.
14
- * The library calls the refresh endpoint itself, so it can tell a rejected
15
- * refresh token (400 or 401: the session is over) from a transient failure
16
- * (network, timeout, 5xx: the session is kept and the caller sees the error).
17
- * A refresh is single flight. A download goes through the app's client, so it
18
- * reaches this only when that client is userClient.
19
- */
20
-
21
- /**
22
- * Minimal Redux-store shape required by the user client to dispatch logout.
23
- */
24
-
25
- let store;
26
- let auth;
27
- const injectStore = _store => {
28
- store = _store;
29
- };
30
- exports.injectStore = injectStore;
31
- const injectUserAuth = options => {
32
- auth = options;
33
- };
34
-
35
- // Single promise to track token refresh
36
- exports.injectUserAuth = injectUserAuth;
37
- let refreshTokenPromise = null;
38
-
39
- // Queue for failed requests
40
- let failedQueue = [];
41
-
42
- /**
43
- * Processes the queue of failed requests.
44
- */
45
- const processQueue = (error, token = null) => {
46
- failedQueue.forEach(({
47
- resolve,
48
- reject
49
- }) => {
50
- if (error) {
51
- reject(error);
52
- } else if (token !== null) {
53
- resolve(token);
54
- }
55
- });
56
- failedQueue = [];
57
- };
58
-
59
- /**
60
- * Clear any in-flight token refresh and reject everything queued behind it.
61
- * Call this on logout.
62
- *
63
- * Without it, a refresh that was already in flight when the user logged out
64
- * resolves afterwards and replays every queued request with a token for a
65
- * session that has ended. The queue is module state, so it survives the logout
66
- * entirely: nothing else clears it. A consumer app hit this and hand-wrote the
67
- * same function locally, which is how it was found.
68
- */
69
- const resetRefreshState = () => {
70
- refreshTokenPromise = null;
71
- processQueue(new Error('[@webority/mobile-core] Session ended'));
72
- };
73
- exports.resetRefreshState = resetRefreshState;
74
- const getErrorMessage = error => {
75
- if (error instanceof Error) {
76
- return error.message;
77
- }
78
- if (typeof error === 'string') {
79
- return error;
80
- }
81
- return 'Unknown error';
82
- };
83
- exports.getErrorMessage = getErrorMessage;
84
- const requireAuth = () => {
85
- if (!store || !auth) {
86
- throw new Error('User package store is not initialized. Call initUserAuth first.');
87
- }
88
- return {
89
- store,
90
- auth
91
- };
92
- };
93
-
94
- /** The refresh endpoint's path, once initUserAuth has run. */
95
- const getRefreshPath = () => auth?.refresh.path;
96
- exports.getRefreshPath = getRefreshPath;
97
- const dispatchLogout = async () => {
98
- const wired = requireAuth();
99
- await wired.store.dispatch(wired.auth.logout());
100
- };
101
- exports.dispatchLogout = dispatchLogout;
102
- const isRefreshInFlight = () => refreshTokenPromise !== null;
103
-
104
- /** Wait for the in-flight refresh without joining it: resolved or rejected by processQueue. */
105
- exports.isRefreshInFlight = isRefreshInFlight;
106
- const queueBehindRefresh = () => new Promise((resolve, reject) => {
107
- failedQueue.push({
108
- resolve,
109
- reject
110
- });
111
- });
112
-
113
- /** A failure that proves the session is over, as opposed to one worth retrying later. */
114
- exports.queueBehindRefresh = queueBehindRefresh;
115
- class SessionEndedError extends Error {
116
- constructor(message) {
117
- super(message);
118
- this.name = 'SessionEndedError';
119
- // Babel's class transform can drop the prototype link, which makes
120
- // `instanceof SessionEndedError` false in the transpiled build.
121
- Object.setPrototypeOf(this, SessionEndedError.prototype);
122
- }
123
- }
124
- const endsSession = error => error instanceof SessionEndedError || _axios.default.isAxiosError(error) && (error.response?.status === 400 || error.response?.status === 401);
125
- const asString = (value, field) => {
126
- if (typeof value !== 'string' || value === '') {
127
- throw new Error(`The refresh response has no "${field}".`);
128
- }
129
- return value;
130
- };
131
-
132
- /** The Webority API's refresh response. */
133
- const readWeborityTokens = data => {
134
- const body = data ?? {};
135
- return {
136
- accessToken: asString(body.accessToken, 'accessToken'),
137
- accessTokenExpiry: asString(body.accessTokenExpiration, 'accessTokenExpiration'),
138
- refreshToken: asString(body.refreshToken, 'refreshToken'),
139
- refreshTokenExpiry: asString(body.refreshTokenExpiration, 'refreshTokenExpiration')
140
- };
141
- };
142
- const callRefreshEndpoint = async refresh => {
143
- const refreshToken = await (0, _authStore.getToken)(_authStore.REFRESH_TOKEN_KEY);
144
- if (!refreshToken?.token) {
145
- throw new SessionEndedError('No refresh token available');
146
- }
147
- // An expired refresh token would only earn a 401 from the endpoint before the same logout.
148
- if (Date.parse(refreshToken.expiry) <= Date.now()) {
149
- throw new SessionEndedError('Refresh token expired');
150
- }
151
- const accessToken = await (0, _authStore.getToken)(_authStore.ACCESS_TOKEN_KEY);
152
- const tokens = {
153
- accessToken: accessToken?.token ?? null,
154
- refreshToken: refreshToken.token
155
- };
156
- const body = refresh.body ? await refresh.body(tokens) : tokens;
157
- _index.Logger.info('Refreshing the access token');
158
- const {
159
- data
160
- } = await _publicClient.default.post(refresh.path, body);
161
- const next = (refresh.readTokens ?? readWeborityTokens)(data);
162
- await (0, _authStore.storeToken)(_authStore.ACCESS_TOKEN_KEY, next.accessToken, next.accessTokenExpiry);
163
- await (0, _authStore.storeToken)(_authStore.REFRESH_TOKEN_KEY, next.refreshToken, next.refreshTokenExpiry);
164
- _index.Logger.info('Access token refreshed');
165
- return next.accessToken;
166
- };
167
-
168
- /**
169
- * Refresh the access token, joining a refresh already in flight rather than
170
- * starting a second one. Every request queued behind it is replayed on success
171
- * and rejected on failure. Logs out only when the failure proves the session
172
- * is over; a network error, timeout or 5xx keeps it and rejects.
173
- */
174
- const refreshAccessToken = () => {
175
- if (refreshTokenPromise) {
176
- return refreshTokenPromise;
177
- }
178
- const run = async () => {
179
- try {
180
- const token = await callRefreshEndpoint(requireAuth().auth.refresh);
181
- processQueue(null, token);
182
- return token;
183
- } catch (error) {
184
- const responseData = _axios.default.isAxiosError(error) ? error.response?.data : undefined;
185
- _index.Logger.error('Token refresh failed:', responseData ?? getErrorMessage(error));
186
- processQueue(error, null);
187
- if (endsSession(error)) {
188
- await dispatchLogout();
189
- }
190
- throw error;
191
- }
192
- };
193
- // Cleared in a chained finally, not inside run: a failure before run's first
194
- // await would otherwise clear the slot before it was assigned, leaving a
195
- // rejected promise that every later refresh joins.
196
- const promise = run().finally(() => {
197
- if (refreshTokenPromise === promise) {
198
- refreshTokenPromise = null;
199
- }
200
- });
201
- refreshTokenPromise = promise;
202
- return promise;
203
- };
204
- exports.refreshAccessToken = refreshAccessToken;
205
- //# sourceMappingURL=tokenRefresh.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.resetRefreshState=exports.refreshAccessToken=exports.readSignOutReason=exports.queueBehindRefresh=exports.isRefreshInFlight=exports.injectUserAuth=exports.injectStore=exports.getRefreshPath=exports.getErrorMessage=exports.getAccountIdProvider=exports.endSessionAndLogout=exports.dispatchLogout=exports.SIGN_OUT_REASON_HEADER=void 0;var _axios=_interopRequireDefault(require("axios")),_authStore=require("../auth/authStore.js"),_index=require("../logger/index.js"),_publicClient=_interopRequireDefault(require("./publicClient.js"));function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}let store,auth;const injectStore=e=>{store=e};exports.injectStore=injectStore;const injectUserAuth=e=>{auth=e};exports.injectUserAuth=injectUserAuth;let refreshTokenPromise=null,failedQueue=[];const processQueue=(e,r=null)=>{failedQueue.forEach(({resolve:s,reject:t})=>{e?t(e):null!==r&&s(r)}),failedQueue=[]},resetRefreshState=()=>{refreshTokenPromise=null,processQueue(new Error("[@webority/mobile-core] Session ended"))};exports.resetRefreshState=resetRefreshState;const getErrorMessage=e=>e instanceof Error?e.message:"string"==typeof e?e:"Unknown error";exports.getErrorMessage=getErrorMessage;const requireAuth=()=>{if(!store||!auth)throw new Error("User package store is not initialized. Call initUserAuth first.");return{store:store,auth:auth}},getAccountIdProvider=()=>auth?.accountId;exports.getAccountIdProvider=getAccountIdProvider;const getRefreshPath=()=>auth?.refresh.path;exports.getRefreshPath=getRefreshPath;const dispatchLogout=async()=>{const e=requireAuth();await e.store.dispatch(e.auth.logout())};exports.dispatchLogout=dispatchLogout;const SIGN_OUT_REASON_HEADER=exports.SIGN_OUT_REASON_HEADER="X-Sign-Out-Reason",readSignOutReason=e=>{const r=void 0===auth?.signOutReasonHeader?SIGN_OUT_REASON_HEADER:auth.signOutReasonHeader;if(!r||!e||"object"!=typeof e)return null;const s=e.get,t="function"==typeof s?s.call(e,r):e[r.toLowerCase()];return"string"==typeof t&&""!==t.trim()?t.trim():null};exports.readSignOutReason=readSignOutReason;const endSessionAndLogout=async(e,r)=>{await(0,_authStore.clearSession)(e,{signOutReason:r}),await dispatchLogout()};exports.endSessionAndLogout=endSessionAndLogout;const isRefreshInFlight=()=>null!==refreshTokenPromise;exports.isRefreshInFlight=isRefreshInFlight;const queueBehindRefresh=()=>new Promise((e,r)=>{failedQueue.push({resolve:e,reject:r})});exports.queueBehindRefresh=queueBehindRefresh;class SessionEndedError extends Error{constructor(e){super(e),this.name="SessionEndedError",Object.setPrototypeOf(this,SessionEndedError.prototype)}}const endsSession=e=>e instanceof SessionEndedError||_axios.default.isAxiosError(e)&&(400===e.response?.status||401===e.response?.status),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 r=await(0,_authStore.getToken)(_authStore.REFRESH_TOKEN_KEY);if(!r?.token)throw new SessionEndedError("No refresh token available");if(Date.parse(r.expiry)<=Date.now())throw new SessionEndedError("Refresh token expired");const s=await(0,_authStore.getToken)(_authStore.ACCESS_TOKEN_KEY),t={accessToken:s?.token??null,refreshToken:r.token},o=e.body?await e.body(t):t,n=e.headers?await e.headers():void 0;_index.Logger.info("Refreshing the access token");const{data:i}=await _publicClient.default.post(e.path,o,n?{headers:n}:void 0),a=(e.readTokens??readWeborityTokens)(i);return await(0,_authStore.storeToken)(_authStore.ACCESS_TOKEN_KEY,a.accessToken,a.accessTokenExpiry),await(0,_authStore.storeToken)(_authStore.REFRESH_TOKEN_KEY,a.refreshToken,a.refreshTokenExpiry),_index.Logger.info("Access token refreshed"),a.accessToken},refreshAccessToken=()=>{if(refreshTokenPromise)return refreshTokenPromise;const e=(async()=>{try{const e=await callRefreshEndpoint(requireAuth().auth.refresh);return processQueue(null,e),e}catch(e){const r=_axios.default.isAxiosError(e)?e.response?.data:void 0;throw _index.Logger.error("Token refresh failed:",r??getErrorMessage(e)),processQueue(e,null),endsSession(e)&&await endSessionAndLogout("rejected",_axios.default.isAxiosError(e)?readSignOutReason(e.response?.headers):null),e}})().finally(()=>{refreshTokenPromise===e&&(refreshTokenPromise=null)});return refreshTokenPromise=e,e};exports.refreshAccessToken=refreshAccessToken;