@webority/mobile-core 0.0.23

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 (211) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +39 -0
  3. package/android/build.gradle +65 -0
  4. package/android/src/newarch/java/com/webority/mobilecore/smsretriever/AppSignatureHelper.kt +57 -0
  5. package/android/src/newarch/java/com/webority/mobilecore/smsretriever/SmsRetrieverModule.kt +20 -0
  6. package/android/src/newarch/java/com/webority/mobilecore/smsretriever/SmsRetrieverModuleImpl.kt +146 -0
  7. package/android/src/newarch/java/com/webority/mobilecore/smsretriever/SmsRetrieverPackage.kt +33 -0
  8. package/lib/commonjs/api/baseUrl.js +34 -0
  9. package/lib/commonjs/api/curl.js +109 -0
  10. package/lib/commonjs/api/publicClient.js +73 -0
  11. package/lib/commonjs/api/retry.js +75 -0
  12. package/lib/commonjs/api/tokenRefresh.js +205 -0
  13. package/lib/commonjs/api/userClient.js +255 -0
  14. package/lib/commonjs/auth/authStore.js +232 -0
  15. package/lib/commonjs/auth/index.js +44 -0
  16. package/lib/commonjs/auth/jwt.js +112 -0
  17. package/lib/commonjs/config/index.js +66 -0
  18. package/lib/commonjs/deepLink/index.js +286 -0
  19. package/lib/commonjs/download/adapters/expoFs.js +69 -0
  20. package/lib/commonjs/download/adapters/shared.js +13 -0
  21. package/lib/commonjs/download/index.js +250 -0
  22. package/lib/commonjs/formatters/currency.js +69 -0
  23. package/lib/commonjs/formatters/date.js +168 -0
  24. package/lib/commonjs/formatters/dateMath.js +310 -0
  25. package/lib/commonjs/formatters/index.js +246 -0
  26. package/lib/commonjs/formatters/initials.js +32 -0
  27. package/lib/commonjs/formatters/number.js +88 -0
  28. package/lib/commonjs/formatters/phone.js +369 -0
  29. package/lib/commonjs/formatters/windowsToIanaTable.js +164 -0
  30. package/lib/commonjs/geolocation/adapters/expoLocation.js +126 -0
  31. package/lib/commonjs/geolocation/index.js +355 -0
  32. package/lib/commonjs/index.js +630 -0
  33. package/lib/commonjs/initSDK.js +66 -0
  34. package/lib/commonjs/initUserAuth.js +37 -0
  35. package/lib/commonjs/logger/index.js +291 -0
  36. package/lib/commonjs/network/index.js +31 -0
  37. package/lib/commonjs/network/networkStatus.js +133 -0
  38. package/lib/commonjs/package.json +1 -0
  39. package/lib/commonjs/permissions/calendar.js +14 -0
  40. package/lib/commonjs/permissions/camera.js +14 -0
  41. package/lib/commonjs/permissions/contacts.js +10 -0
  42. package/lib/commonjs/permissions/index.js +133 -0
  43. package/lib/commonjs/permissions/location.js +14 -0
  44. package/lib/commonjs/permissions/mediaLibrary.js +10 -0
  45. package/lib/commonjs/permissions/notifications.js +10 -0
  46. package/lib/commonjs/permissions/photos.js +14 -0
  47. package/lib/commonjs/phone/countries.js +265 -0
  48. package/lib/commonjs/share/index.js +70 -0
  49. package/lib/commonjs/smsRetriever/index.js +91 -0
  50. package/lib/commonjs/specs/NativeSmsRetriever.js +15 -0
  51. package/lib/commonjs/sqlite/adapters/expo.js +53 -0
  52. package/lib/commonjs/sqlite/index.js +604 -0
  53. package/lib/commonjs/storage/index.js +214 -0
  54. package/lib/commonjs/types/globals.d.js +6 -0
  55. package/lib/commonjs/utils/imageCompression.js +389 -0
  56. package/lib/commonjs/utils/index.js +19 -0
  57. package/lib/commonjs/validators/index.js +227 -0
  58. package/lib/commonjs/versionCheck/index.js +419 -0
  59. package/lib/module/api/baseUrl.js +30 -0
  60. package/lib/module/api/curl.js +104 -0
  61. package/lib/module/api/publicClient.js +69 -0
  62. package/lib/module/api/retry.js +69 -0
  63. package/lib/module/api/tokenRefresh.js +192 -0
  64. package/lib/module/api/userClient.js +233 -0
  65. package/lib/module/auth/authStore.js +224 -0
  66. package/lib/module/auth/index.js +5 -0
  67. package/lib/module/auth/jwt.js +106 -0
  68. package/lib/module/config/index.js +58 -0
  69. package/lib/module/deepLink/index.js +280 -0
  70. package/lib/module/download/adapters/expoFs.js +64 -0
  71. package/lib/module/download/adapters/shared.js +8 -0
  72. package/lib/module/download/index.js +242 -0
  73. package/lib/module/formatters/currency.js +64 -0
  74. package/lib/module/formatters/date.js +160 -0
  75. package/lib/module/formatters/dateMath.js +294 -0
  76. package/lib/module/formatters/index.js +9 -0
  77. package/lib/module/formatters/initials.js +27 -0
  78. package/lib/module/formatters/number.js +81 -0
  79. package/lib/module/formatters/phone.js +302 -0
  80. package/lib/module/formatters/windowsToIanaTable.js +160 -0
  81. package/lib/module/geolocation/adapters/expoLocation.js +121 -0
  82. package/lib/module/geolocation/index.js +347 -0
  83. package/lib/module/index.js +54 -0
  84. package/lib/module/initSDK.js +61 -0
  85. package/lib/module/initUserAuth.js +33 -0
  86. package/lib/module/logger/index.js +285 -0
  87. package/lib/module/network/index.js +4 -0
  88. package/lib/module/network/networkStatus.js +124 -0
  89. package/lib/module/package.json +1 -0
  90. package/lib/module/permissions/calendar.js +15 -0
  91. package/lib/module/permissions/camera.js +15 -0
  92. package/lib/module/permissions/contacts.js +11 -0
  93. package/lib/module/permissions/index.js +128 -0
  94. package/lib/module/permissions/location.js +15 -0
  95. package/lib/module/permissions/mediaLibrary.js +11 -0
  96. package/lib/module/permissions/notifications.js +11 -0
  97. package/lib/module/permissions/photos.js +15 -0
  98. package/lib/module/phone/countries.js +261 -0
  99. package/lib/module/share/index.js +64 -0
  100. package/lib/module/smsRetriever/index.js +86 -0
  101. package/lib/module/specs/NativeSmsRetriever.js +13 -0
  102. package/lib/module/sqlite/adapters/expo.js +48 -0
  103. package/lib/module/sqlite/index.js +594 -0
  104. package/lib/module/storage/index.js +208 -0
  105. package/lib/module/types/globals.d.js +10 -0
  106. package/lib/module/utils/imageCompression.js +383 -0
  107. package/lib/module/utils/index.js +4 -0
  108. package/lib/module/validators/index.js +205 -0
  109. package/lib/module/versionCheck/index.js +413 -0
  110. package/lib/typescript/commonjs/api/baseUrl.d.ts +20 -0
  111. package/lib/typescript/commonjs/api/curl.d.ts +18 -0
  112. package/lib/typescript/commonjs/api/publicClient.d.ts +11 -0
  113. package/lib/typescript/commonjs/api/retry.d.ts +27 -0
  114. package/lib/typescript/commonjs/api/tokenRefresh.d.ts +43 -0
  115. package/lib/typescript/commonjs/api/userClient.d.ts +37 -0
  116. package/lib/typescript/commonjs/auth/authStore.d.ts +79 -0
  117. package/lib/typescript/commonjs/auth/index.d.ts +5 -0
  118. package/lib/typescript/commonjs/auth/jwt.d.ts +31 -0
  119. package/lib/typescript/commonjs/config/index.d.ts +55 -0
  120. package/lib/typescript/commonjs/deepLink/index.d.ts +54 -0
  121. package/lib/typescript/commonjs/download/adapters/expoFs.d.ts +38 -0
  122. package/lib/typescript/commonjs/download/adapters/shared.d.ts +3 -0
  123. package/lib/typescript/commonjs/download/index.d.ts +86 -0
  124. package/lib/typescript/commonjs/formatters/currency.d.ts +24 -0
  125. package/lib/typescript/commonjs/formatters/date.d.ts +22 -0
  126. package/lib/typescript/commonjs/formatters/dateMath.d.ts +88 -0
  127. package/lib/typescript/commonjs/formatters/index.d.ts +10 -0
  128. package/lib/typescript/commonjs/formatters/initials.d.ts +11 -0
  129. package/lib/typescript/commonjs/formatters/number.d.ts +15 -0
  130. package/lib/typescript/commonjs/formatters/phone.d.ts +133 -0
  131. package/lib/typescript/commonjs/formatters/windowsToIanaTable.d.ts +18 -0
  132. package/lib/typescript/commonjs/geolocation/adapters/expoLocation.d.ts +42 -0
  133. package/lib/typescript/commonjs/geolocation/index.d.ts +90 -0
  134. package/lib/typescript/commonjs/index.d.ts +54 -0
  135. package/lib/typescript/commonjs/initSDK.d.ts +72 -0
  136. package/lib/typescript/commonjs/initUserAuth.d.ts +41 -0
  137. package/lib/typescript/commonjs/logger/index.d.ts +81 -0
  138. package/lib/typescript/commonjs/network/index.d.ts +3 -0
  139. package/lib/typescript/commonjs/network/networkStatus.d.ts +43 -0
  140. package/lib/typescript/commonjs/package.json +1 -0
  141. package/lib/typescript/commonjs/permissions/calendar.d.ts +2 -0
  142. package/lib/typescript/commonjs/permissions/camera.d.ts +2 -0
  143. package/lib/typescript/commonjs/permissions/contacts.d.ts +2 -0
  144. package/lib/typescript/commonjs/permissions/index.d.ts +48 -0
  145. package/lib/typescript/commonjs/permissions/location.d.ts +2 -0
  146. package/lib/typescript/commonjs/permissions/mediaLibrary.d.ts +2 -0
  147. package/lib/typescript/commonjs/permissions/notifications.d.ts +2 -0
  148. package/lib/typescript/commonjs/permissions/photos.d.ts +2 -0
  149. package/lib/typescript/commonjs/phone/countries.d.ts +13 -0
  150. package/lib/typescript/commonjs/share/index.d.ts +29 -0
  151. package/lib/typescript/commonjs/smsRetriever/index.d.ts +30 -0
  152. package/lib/typescript/commonjs/specs/NativeSmsRetriever.d.ts +15 -0
  153. package/lib/typescript/commonjs/sqlite/adapters/expo.d.ts +30 -0
  154. package/lib/typescript/commonjs/sqlite/index.d.ts +113 -0
  155. package/lib/typescript/commonjs/storage/index.d.ts +64 -0
  156. package/lib/typescript/commonjs/utils/imageCompression.d.ts +152 -0
  157. package/lib/typescript/commonjs/utils/index.d.ts +3 -0
  158. package/lib/typescript/commonjs/validators/index.d.ts +51 -0
  159. package/lib/typescript/commonjs/versionCheck/index.d.ts +80 -0
  160. package/lib/typescript/module/api/baseUrl.d.ts +20 -0
  161. package/lib/typescript/module/api/curl.d.ts +18 -0
  162. package/lib/typescript/module/api/publicClient.d.ts +11 -0
  163. package/lib/typescript/module/api/retry.d.ts +27 -0
  164. package/lib/typescript/module/api/tokenRefresh.d.ts +43 -0
  165. package/lib/typescript/module/api/userClient.d.ts +37 -0
  166. package/lib/typescript/module/auth/authStore.d.ts +79 -0
  167. package/lib/typescript/module/auth/index.d.ts +5 -0
  168. package/lib/typescript/module/auth/jwt.d.ts +31 -0
  169. package/lib/typescript/module/config/index.d.ts +55 -0
  170. package/lib/typescript/module/deepLink/index.d.ts +54 -0
  171. package/lib/typescript/module/download/adapters/expoFs.d.ts +38 -0
  172. package/lib/typescript/module/download/adapters/shared.d.ts +3 -0
  173. package/lib/typescript/module/download/index.d.ts +86 -0
  174. package/lib/typescript/module/formatters/currency.d.ts +24 -0
  175. package/lib/typescript/module/formatters/date.d.ts +22 -0
  176. package/lib/typescript/module/formatters/dateMath.d.ts +88 -0
  177. package/lib/typescript/module/formatters/index.d.ts +10 -0
  178. package/lib/typescript/module/formatters/initials.d.ts +11 -0
  179. package/lib/typescript/module/formatters/number.d.ts +15 -0
  180. package/lib/typescript/module/formatters/phone.d.ts +133 -0
  181. package/lib/typescript/module/formatters/windowsToIanaTable.d.ts +18 -0
  182. package/lib/typescript/module/geolocation/adapters/expoLocation.d.ts +42 -0
  183. package/lib/typescript/module/geolocation/index.d.ts +90 -0
  184. package/lib/typescript/module/index.d.ts +54 -0
  185. package/lib/typescript/module/initSDK.d.ts +72 -0
  186. package/lib/typescript/module/initUserAuth.d.ts +41 -0
  187. package/lib/typescript/module/logger/index.d.ts +81 -0
  188. package/lib/typescript/module/network/index.d.ts +3 -0
  189. package/lib/typescript/module/network/networkStatus.d.ts +43 -0
  190. package/lib/typescript/module/package.json +1 -0
  191. package/lib/typescript/module/permissions/calendar.d.ts +2 -0
  192. package/lib/typescript/module/permissions/camera.d.ts +2 -0
  193. package/lib/typescript/module/permissions/contacts.d.ts +2 -0
  194. package/lib/typescript/module/permissions/index.d.ts +48 -0
  195. package/lib/typescript/module/permissions/location.d.ts +2 -0
  196. package/lib/typescript/module/permissions/mediaLibrary.d.ts +2 -0
  197. package/lib/typescript/module/permissions/notifications.d.ts +2 -0
  198. package/lib/typescript/module/permissions/photos.d.ts +2 -0
  199. package/lib/typescript/module/phone/countries.d.ts +13 -0
  200. package/lib/typescript/module/share/index.d.ts +29 -0
  201. package/lib/typescript/module/smsRetriever/index.d.ts +30 -0
  202. package/lib/typescript/module/specs/NativeSmsRetriever.d.ts +15 -0
  203. package/lib/typescript/module/sqlite/adapters/expo.d.ts +30 -0
  204. package/lib/typescript/module/sqlite/index.d.ts +113 -0
  205. package/lib/typescript/module/storage/index.d.ts +64 -0
  206. package/lib/typescript/module/utils/imageCompression.d.ts +152 -0
  207. package/lib/typescript/module/utils/index.d.ts +3 -0
  208. package/lib/typescript/module/validators/index.d.ts +51 -0
  209. package/lib/typescript/module/versionCheck/index.d.ts +80 -0
  210. package/package.json +298 -0
  211. package/src/specs/NativeSmsRetriever.ts +16 -0
@@ -0,0 +1,205 @@
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
@@ -0,0 +1,255 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = exports.SESSION_TERMINATION_CODES = exports.SESSION_INVALID_HEADER = exports.RequestOfflineError = void 0;
7
+ Object.defineProperty(exports, "injectStore", {
8
+ enumerable: true,
9
+ get: function () {
10
+ return _tokenRefresh.injectStore;
11
+ }
12
+ });
13
+ Object.defineProperty(exports, "injectUserAuth", {
14
+ enumerable: true,
15
+ get: function () {
16
+ return _tokenRefresh.injectUserAuth;
17
+ }
18
+ });
19
+ Object.defineProperty(exports, "resetRefreshState", {
20
+ enumerable: true,
21
+ get: function () {
22
+ return _tokenRefresh.resetRefreshState;
23
+ }
24
+ });
25
+ var _axios = _interopRequireWildcard(require("axios"));
26
+ var _authStore = require("../auth/authStore.js");
27
+ var _index = require("../config/index.js");
28
+ var _index2 = require("../logger/index.js");
29
+ var _networkStatus = require("../network/networkStatus.js");
30
+ var _baseUrl = require("./baseUrl.js");
31
+ var _curl = require("./curl.js");
32
+ var _retry = require("./retry.js");
33
+ var _tokenRefresh = require("./tokenRefresh.js");
34
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
35
+ // Define timeout for requests
36
+ const REQUEST_TIMEOUT = 30000; // 30 seconds
37
+
38
+ /**
39
+ * Thrown by the request interceptor when the device is offline, before the
40
+ * request ever reaches the adapter (and therefore the network). Distinct from
41
+ * an AxiosError so callers can show an offline message instead of a generic
42
+ * request failure.
43
+ */
44
+ class RequestOfflineError extends Error {
45
+ constructor(message = 'Request blocked: device is offline') {
46
+ super(message);
47
+ this.name = 'RequestOfflineError';
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Sub-codes a 401 response body carries when the session itself is dead
53
+ * (terminated by an admin, or expired server-side) rather than merely
54
+ * out-of-date. These never warrant a refresh attempt — only logout.
55
+ */
56
+ exports.RequestOfflineError = RequestOfflineError;
57
+ const SESSION_TERMINATION_CODES = exports.SESSION_TERMINATION_CODES = {
58
+ TERMINATED: 'SESSION_TERMINATED',
59
+ EXPIRED: 'SESSION_EXPIRED'
60
+ };
61
+ const TERMINATION_CODES = new Set(Object.values(SESSION_TERMINATION_CODES));
62
+
63
+ /**
64
+ * The header is the AUTHORITATIVE signal, verified against the backend:
65
+ * `WeborityJwtMiddleware.ReturnUnauthorized` answers an invalidated session with
66
+ * `401` + `X-Security-Stamp-Invalid: true` and a body of
67
+ * `{ error: 'Unauthorized', message: '...' }` — no code field at all, and
68
+ * `error` is a string rather than an object. An earlier version of this checked
69
+ * only body fields and would therefore never have fired against a real server.
70
+ *
71
+ * The body codes below are kept as a secondary path for any head that does send
72
+ * an explicit sub-code; they match on exact strings, so looking is free.
73
+ */
74
+ const SESSION_INVALID_HEADER = exports.SESSION_INVALID_HEADER = 'x-security-stamp-invalid';
75
+ const hasSessionInvalidHeader = headers => {
76
+ if (!headers || typeof headers !== 'object') {
77
+ return false;
78
+ }
79
+ const get = headers.get;
80
+ const raw = typeof get === 'function' ? get.call(headers, SESSION_INVALID_HEADER) : headers[SESSION_INVALID_HEADER];
81
+ return String(raw).toLowerCase() === 'true';
82
+ };
83
+ const getSessionTerminationCode = data => {
84
+ if (!data || typeof data !== 'object') {
85
+ return undefined;
86
+ }
87
+ const body = data;
88
+ const nested = body.error;
89
+ const candidates = [body.code, body.errorCode, nested && typeof nested === 'object' ? nested.code : undefined];
90
+ for (const candidate of candidates) {
91
+ if (typeof candidate === 'string' && TERMINATION_CODES.has(candidate)) {
92
+ return candidate;
93
+ }
94
+ }
95
+ return undefined;
96
+ };
97
+
98
+ /**
99
+ * Internal extension of axios request config to track retry attempts.
100
+ */
101
+
102
+ const userClient = _axios.default.create({
103
+ headers: {
104
+ 'Content-Type': 'application/json',
105
+ 'Accept-Encoding': 'gzip, deflate, br'
106
+ },
107
+ timeout: REQUEST_TIMEOUT // Set timeout for all requests
108
+ });
109
+
110
+ // Registered before the auth interceptors below, so a retryable 5xx is retried
111
+ // before anything else sees it while a 401 still falls through to the refresh
112
+ // flow. Policy and reasoning live in ./retry.
113
+ (0, _retry.attachRetry)(userClient);
114
+ const setAuthHeader = (config, token) => {
115
+ if (config.headers instanceof _axios.AxiosHeaders) {
116
+ config.headers.set('Authorization', `Bearer ${token}`);
117
+ } else {
118
+ // Fallback in case headers were provided as a plain object.
119
+ config.headers = _axios.AxiosHeaders.from({
120
+ ...config.headers,
121
+ Authorization: `Bearer ${token}`
122
+ });
123
+ }
124
+ };
125
+ const isRefreshTokenRequest = url => {
126
+ const path = (0, _tokenRefresh.getRefreshPath)();
127
+ return !!path && !!url?.includes(path);
128
+ };
129
+
130
+ /**
131
+ * Attach Authorization Token Before Requests
132
+ */
133
+ // Registered after the curl logger and before the auth interceptor, so that it
134
+ // RUNS between them: axios unshifts its request chain, making the first
135
+ // registered run last. The curl line therefore describes a request that already
136
+ // has both its baseURL and its Authorization header.
137
+ (0, _baseUrl.applyConfiguredBaseUrl)(userClient);
138
+
139
+ // Registered BEFORE the auth interceptor below deliberately. Axios builds its
140
+ // request chain by unshifting, so the FIRST-registered request interceptor runs
141
+ // LAST — closest to dispatch. This one therefore observes the final config,
142
+ // with the Authorization header already attached, which is the whole point of
143
+ // a curl line. Registering it after would log the request before it is signed.
144
+ userClient.interceptors.request.use(config => {
145
+ if ((0, _index.getConfigValue)('logCurl')) {
146
+ (0, _curl.logCurl)(config);
147
+ }
148
+ return config;
149
+ });
150
+ userClient.interceptors.request.use(async config => {
151
+ // Reject before any network call when the device is known to be offline.
152
+ // Unknown connectivity (netinfo not installed, or undetermined) is treated
153
+ // as online — never block a request on an unavailable probe.
154
+ const network = await (0, _networkStatus.getNetworkStatus)();
155
+ if (network.isConnected === false) {
156
+ _index2.Logger.warn(`Request blocked: device is offline. URL: ${config.url}`);
157
+ return Promise.reject(new RequestOfflineError());
158
+ }
159
+ try {
160
+ // Skip token check for refresh token endpoint to prevent infinite loop
161
+ if (isRefreshTokenRequest(config.url)) {
162
+ return config;
163
+ }
164
+ const token = await (0, _authStore.getToken)(_authStore.ACCESS_TOKEN_KEY);
165
+
166
+ // Check if token exists
167
+ if (!token?.token) {
168
+ throw new Error('No access token available');
169
+ }
170
+
171
+ // Check if token is expired
172
+ const currentTime = Date.now();
173
+ const tokenExpiry = Date.parse(token.expiry);
174
+ const bufferTime = 30 * 1000; // 30 seconds buffer
175
+
176
+ // If token is expired or about to expire, try to refresh it first
177
+ if (tokenExpiry <= currentTime + bufferTime) {
178
+ _index2.Logger.info('Access token expired or about to expire, refreshing before request');
179
+
180
+ // Joins a refresh already in flight rather than starting a second one.
181
+ const newToken = await (0, _tokenRefresh.refreshAccessToken)();
182
+ setAuthHeader(config, newToken);
183
+ return config;
184
+ }
185
+
186
+ // Token is valid, proceed with request
187
+ setAuthHeader(config, token.token);
188
+ return config;
189
+ } catch (error) {
190
+ _index2.Logger.error('Failed to attach token:', (0, _tokenRefresh.getErrorMessage)(error));
191
+ return Promise.reject(error);
192
+ }
193
+ }, error => {
194
+ _index2.Logger.error('Request error:', (0, _tokenRefresh.getErrorMessage)(error));
195
+ return Promise.reject(error);
196
+ });
197
+
198
+ /**
199
+ * Handle Response & Token Refresh
200
+ */
201
+ userClient.interceptors.response.use(response => response, async error => {
202
+ if (!_axios.default.isAxiosError(error)) {
203
+ _index2.Logger.error(`Non-axios error in response interceptor: ${(0, _tokenRefresh.getErrorMessage)(error)}`);
204
+ return Promise.reject(error);
205
+ }
206
+ const axiosError = error;
207
+ const originalRequest = axiosError.config;
208
+ if (!axiosError.response) {
209
+ _index2.Logger.error(`Network error or no response from server: ${axiosError.message}, URL: ${originalRequest?.url}`);
210
+ return Promise.reject(error);
211
+ }
212
+ if (axiosError.response.status === 401 && originalRequest && !originalRequest._retry) {
213
+ // The refresh call goes through this client. A 401 on it must not queue behind
214
+ // refreshTokenPromise: that promise is the call itself, so queueing deadlocks it
215
+ // and every later 401 waits forever.
216
+ if (isRefreshTokenRequest(originalRequest.url)) {
217
+ return Promise.reject(error);
218
+ }
219
+ const terminationCode = getSessionTerminationCode(axiosError.response.data) ?? (hasSessionInvalidHeader(axiosError.response.headers) ? SESSION_TERMINATION_CODES.TERMINATED : undefined);
220
+ if (terminationCode) {
221
+ // The session is dead server-side — a refresh would only burn a
222
+ // pointless round-trip, so go straight to logout.
223
+ _index2.Logger.warn(`Session ${terminationCode}, logging out without refresh. URL: ${originalRequest.url}`);
224
+ (0, _tokenRefresh.resetRefreshState)();
225
+ await (0, _tokenRefresh.dispatchLogout)();
226
+ return Promise.reject(error);
227
+ }
228
+ _index2.Logger.warn(`Unauthorized URL: ${originalRequest.url}`);
229
+ originalRequest._retry = true;
230
+
231
+ // If we already have a refresh in progress, queue this request
232
+ if ((0, _tokenRefresh.isRefreshInFlight)()) {
233
+ return (0, _tokenRefresh.queueBehindRefresh)().then(token => {
234
+ _index2.Logger.info('Retrying request after token refresh:', originalRequest.url);
235
+ setAuthHeader(originalRequest, token);
236
+ return userClient(originalRequest);
237
+ }).catch(err => {
238
+ _index2.Logger.error('Failed to retry request after token refresh:', (0, _tokenRefresh.getErrorMessage)(err));
239
+ return Promise.reject(err);
240
+ });
241
+ }
242
+ try {
243
+ // Start a new token refresh process and wait for it to complete
244
+ const newToken = await (0, _tokenRefresh.refreshAccessToken)();
245
+ setAuthHeader(originalRequest, newToken);
246
+ return userClient(originalRequest);
247
+ } catch (err) {
248
+ return Promise.reject(err);
249
+ }
250
+ }
251
+ _index2.Logger.error(`Response error: ${axiosError.response?.data ?? axiosError.message}, URL: ${originalRequest?.url}, Status: ${axiosError.response.status}`);
252
+ return Promise.reject(error);
253
+ });
254
+ var _default = exports.default = userClient;
255
+ //# sourceMappingURL=userClient.js.map
@@ -0,0 +1,232 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.storeToken = exports.setKeychainImplementation = exports.removeToken = exports.getToken = exports.REFRESH_TOKEN_KEY = exports.ACCESS_TOKEN_KEY = void 0;
7
+ var SecureStore = _interopRequireWildcard(require("expo-secure-store"));
8
+ var _index = require("../logger/index.js");
9
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
10
+ /**
11
+ * Token data persisted in the keychain.
12
+ */
13
+ /**
14
+ * The two keys the library itself reads. Everything in the library that needs
15
+ * the bearer token, the HTTP client and the authenticated image cache,
16
+ * resolves it through these rather than repeating the literal, so
17
+ * one of them cannot silently drift and start reading a key nothing writes.
18
+ * Apps are free to store their own tokens under any other key.
19
+ */
20
+ const ACCESS_TOKEN_KEY = exports.ACCESS_TOKEN_KEY = 'accessToken';
21
+ const REFRESH_TOKEN_KEY = exports.REFRESH_TOKEN_KEY = 'refreshToken';
22
+
23
+ /**
24
+ * The shape this module speaks to its store through. expo-secure-store is
25
+ * adapted to it below; an app or a test can supply anything else of this shape
26
+ * through `setKeychainImplementation`.
27
+ */
28
+ /**
29
+ * The GENERIC password API, not the internet-credentials one.
30
+ *
31
+ * `kSecClassInternetPassword` exists for credentials bound to a server, protocol
32
+ * and port, and it integrates with Safari and AutoFill. A bearer token keyed by
33
+ * a name like "accessToken" is none of those things, and the library was only
34
+ * satisfying that API's required `server` argument by passing the key twice,
35
+ * once as the server and once as the service. That is misuse of the class.
36
+ *
37
+ * `kSecClassGenericPassword` is the class for app-local secrets, which is what a
38
+ * token is. It is what expo-secure-store uses underneath, and what the consumer
39
+ * app being migrated had already chosen for itself.
40
+ */
41
+ /**
42
+ * The credential shape the seam reads back, declared here rather than imported
43
+ * from whichever package a consumer's backend comes from. A type-only import of
44
+ * a package this library does not declare survives into the published `.d.ts`,
45
+ * where a consumer that has not installed it either fails to typecheck or,
46
+ * under the RN template's `skipLibCheck: true`, loses all checking on these
47
+ * members with no signal. Any credential object with these three members
48
+ * satisfies it structurally.
49
+ */
50
+
51
+ // Only what a test or the showcase injected; null means use expo-secure-store.
52
+ let backend = null;
53
+
54
+ /**
55
+ * expo-secure-store, adapted to the generic-password shape this module speaks.
56
+ *
57
+ * It is a flat key/value store, so the credential shape is reconstructed around
58
+ * it. The key is sanitised because expo rejects anything outside
59
+ * [A-Za-z0-9._-] at runtime, which would otherwise surface as a throw from deep
60
+ * inside the store rather than as a bad key.
61
+ */
62
+ const keyFor = service => (service ?? 'default').replace(/[^A-Za-z0-9._-]/g, '_');
63
+ const expoSecureStoreBackend = {
64
+ setGenericPassword: async (username, password, options) => {
65
+ await SecureStore.setItemAsync(keyFor(options?.service), JSON.stringify({
66
+ username,
67
+ password
68
+ }));
69
+ return true;
70
+ },
71
+ getGenericPassword: async options => {
72
+ const service = options?.service;
73
+ const raw = await SecureStore.getItemAsync(keyFor(service));
74
+ if (raw === null) {
75
+ return false;
76
+ }
77
+ const parsed = JSON.parse(raw);
78
+ return {
79
+ username: parsed.username,
80
+ password: parsed.password,
81
+ service: service ?? 'default'
82
+ };
83
+ },
84
+ resetGenericPassword: async options => {
85
+ await SecureStore.deleteItemAsync(keyFor(options?.service));
86
+ return true;
87
+ }
88
+ };
89
+ const resolveBackend = () => backend ?? expoSecureStoreBackend;
90
+ const requireBackend = op => {
91
+ const b = resolveBackend();
92
+ if (!b) {
93
+ const err = new Error(`[authStore] no secure-store backend available; cannot ${op}.`);
94
+ _index.Logger.error(err.message);
95
+ throw err;
96
+ }
97
+ return b;
98
+ };
99
+
100
+ /**
101
+ * Replace the secure-store backend. Intended for TESTS and for the showcase,
102
+ * which injects a failing store to demo the write-failure path; it is not how
103
+ * an app configures the library. Pass `null` to restore expo-secure-store.
104
+ */
105
+ const setKeychainImplementation = impl => {
106
+ backend = impl;
107
+ };
108
+
109
+ /**
110
+ * Store token with expiry.
111
+ */
112
+ exports.setKeychainImplementation = setKeychainImplementation;
113
+ const storeToken = async (key, token, expiry) => {
114
+ // Never log the token or expiry values: Logger forwards to whatever remote
115
+ // sink the app wired (Crashlytics/Sentry), so a token here leaves the device.
116
+ _index.Logger.info(`Storing token for key: ${key}`);
117
+ try {
118
+ const data = JSON.stringify({
119
+ token,
120
+ expiry
121
+ });
122
+ await requireBackend('store a token').setGenericPassword('token', data, {
123
+ service: key
124
+ });
125
+ _index.Logger.info(`Token stored successfully for key: ${key}`);
126
+ } catch (error) {
127
+ // Log AND rethrow. Swallowing this told the caller the token was persisted
128
+ // when it was not, so an app would show a signed-in user holding a token
129
+ // that never reached the keychain — the session then dies at the next
130
+ // cold start with no failure anyone could have seen.
131
+ _index.Logger.error(`Error storing token for key: ${key}`, error);
132
+ throw error;
133
+ }
134
+ };
135
+
136
+ /**
137
+ * Parse token data from credentials.
138
+ */
139
+ exports.storeToken = storeToken;
140
+ const parseTokenData = credentials => {
141
+ if (!credentials) {
142
+ return null;
143
+ }
144
+ const {
145
+ password
146
+ } = credentials;
147
+ try {
148
+ const parsed = JSON.parse(password);
149
+ if (parsed && typeof parsed === 'object' && 'token' in parsed && 'expiry' in parsed && typeof parsed.token === 'string' && typeof parsed.expiry === 'string') {
150
+ return parsed;
151
+ }
152
+ return null;
153
+ } catch {
154
+ // The single report for "stored, but not a token" belongs to getToken, which
155
+ // knows whether anything was stored at all. Logging here too made one event
156
+ // arrive twice, at two different levels.
157
+ return null;
158
+ }
159
+ };
160
+
161
+ /**
162
+ * Retrieve token and check if expired.
163
+ */
164
+ const getToken = async key => {
165
+ try {
166
+ const credentials = await requireBackend('read a token').getGenericPassword({
167
+ service: key
168
+ });
169
+ // Nothing stored is an ORDINARY state, not a fault: a signed-out user has no
170
+ // token, and every authenticated request, image and download asks for one.
171
+ // Warning here put three info lines and a warning into the log, and into the
172
+ // remote crash sink, on every one of those calls.
173
+ if (!credentials) {
174
+ return null;
175
+ }
176
+ const parsedData = parseTokenData(credentials);
177
+ if (!parsedData) {
178
+ // Something IS stored and it is not a token. That is corruption worth
179
+ // hearing about, and it is a different event from having no token.
180
+ _index.Logger.warn(`Stored credentials for key "${key}" are not a valid token; ignoring them.`);
181
+ return null;
182
+ }
183
+ const {
184
+ token,
185
+ expiry
186
+ } = parsedData;
187
+ return {
188
+ token,
189
+ expiry
190
+ };
191
+ } catch (error) {
192
+ const message = error instanceof Error ? error.message : String(error);
193
+ // Both spellings matter. Android throws "Key permanently invalidated" when
194
+ // the biometric enrolment changes; iOS throws "Secret invalidated" for the
195
+ // equivalent. Either way the stored value can never be decrypted again, so
196
+ // clearing it is the only way out: leaving it makes every later read fail
197
+ // identically, and the user is stuck with no route back to a login. A
198
+ // consumer app handled the iOS spelling and the library did not.
199
+ if (message.includes('Key permanently invalidated') || message.includes('Secret invalidated')) {
200
+ _index.Logger.error(`Stored token for key "${key}" can no longer be decrypted; clearing it.`);
201
+ await removeToken(key);
202
+ return null;
203
+ }
204
+ _index.Logger.error(`Error retrieving token for key: ${key}`, error);
205
+ return null;
206
+ }
207
+ };
208
+
209
+ /**
210
+ * Remove token by key.
211
+ */
212
+ exports.getToken = getToken;
213
+ const removeToken = async key => {
214
+ try {
215
+ await requireBackend('remove a token').resetGenericPassword({
216
+ service: key
217
+ });
218
+ _index.Logger.info(`Token removed successfully for key: ${key}`);
219
+ } catch (error) {
220
+ // Clearing a key that was never written is the ordinary logout path, not a
221
+ // failure: only one of the two tokens may ever have been stored. Logging it
222
+ // as an error puts routine traffic in the channel people watch for real
223
+ // problems, which is how an error channel stops being watched.
224
+ if (error instanceof Error && error.message.includes('Item not found')) {
225
+ _index.Logger.info(`No token found to remove for key: ${key}`);
226
+ return;
227
+ }
228
+ _index.Logger.error(`Failed to remove token for key: ${key}`, error);
229
+ }
230
+ };
231
+ exports.removeToken = removeToken;
232
+ //# sourceMappingURL=authStore.js.map