@techstuff-dev/foundation-api-utils 2.4.0 → 2.7.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.
package/README.md CHANGED
@@ -256,4 +256,4 @@ ISC
256
256
 
257
257
  ## TEST
258
258
 
259
- - bump version
259
+ - bump version 2
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var toolkit = require('@reduxjs/toolkit');
4
- var slice = require('./slice-C4MKCa5N.js');
4
+ var slice = require('./slice-BZ9cHcE8.js');
5
5
  var slice$1 = require('./slice-CkWobkWw.js');
6
6
 
7
7
  // This file exists just so TypeScript has a module at ./store for IDEs/build.
@@ -11,4 +11,4 @@ var slice$1 = require('./slice-CkWobkWw.js');
11
11
  const rootReducer = toolkit.combineSlices(slice.cartSlice, slice$1.authSlice, slice.authApi, slice.contentApi, slice.paymentApi, slice.productsApi, slice.ordersApi);
12
12
 
13
13
  exports.rootReducer = rootReducer;
14
- //# sourceMappingURL=shared-DVGP3iZ0.js.map
14
+ //# sourceMappingURL=shared-BB1LJXHU.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"shared-DVGP3iZ0.js","sources":["../../../lib/store/shared.ts"],"sourcesContent":[null],"names":["combineSlices","cartSlice","authSlice","authApi","contentApi","paymentApi","productsApi","ordersApi"],"mappings":";;;;;;AAAA;AACA;AAUA;AAEA;MACa,WAAW,GAAGA,qBAAa,CACtCC,eAAS,EACTC,iBAAS,EACTC,aAAO,EACPC,gBAAU,EACVC,gBAAU,EACVC,iBAAW,EACXC,eAAS;;;;"}
1
+ {"version":3,"file":"shared-BB1LJXHU.js","sources":["../../../lib/store/shared.ts"],"sourcesContent":[null],"names":["combineSlices","cartSlice","authSlice","authApi","contentApi","paymentApi","productsApi","ordersApi"],"mappings":";;;;;;AAAA;AACA;AAUA;AAEA;MACa,WAAW,GAAGA,qBAAa,CACtCC,eAAS,EACTC,iBAAS,EACTC,aAAO,EACPC,gBAAU,EACVC,gBAAU,EACVC,iBAAW,EACXC,eAAS;;;;"}
@@ -5095,18 +5095,6 @@ function createUnwrappingBaseQuery(baseQueryOptions) {
5095
5095
  };
5096
5096
  }
5097
5097
 
5098
- /**
5099
- * Shared helper to extract auth tokens from the Redux store.
5100
- * Used by all RTK Query services to attach tokens to request headers
5101
- * without depending on aws-amplify's Auth.currentSession().
5102
- */
5103
- function getAuthHeaders(getState) {
5104
- const state = getState();
5105
- const accessToken = state?.auth?.user?.accessToken;
5106
- const idToken = state?.auth?.user?.idToken;
5107
- return { accessToken, idToken };
5108
- }
5109
-
5110
5098
  /**
5111
5099
  * Platform detection utilities for cross-platform React/React Native applications
5112
5100
  */
@@ -5342,44 +5330,52 @@ apiConfig.APP_ES_CHALLENGES_INDEX;
5342
5330
  apiConfig.APP_ES_CHALLENGE_DAYS_INDEX;
5343
5331
  apiConfig.PLATFORM;
5344
5332
 
5345
- // Create a dynamic baseQuery that resolves URL at request time and unwraps standard responses
5346
- const createAuthBaseQuery = () => {
5347
- const baseUrl = typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_API_AUTH_PREFIX
5348
- ? process.env.NEXT_PUBLIC_API_AUTH_PREFIX
5349
- : (API_AUTH_PREFIX || '');
5350
- return createUnwrappingBaseQuery({
5351
- baseUrl,
5352
- prepareHeaders: (headers, { getState }) => {
5353
- headers.set('Content-Type', 'application/json');
5354
- const { accessToken, idToken } = getAuthHeaders(getState);
5355
- if (accessToken && idToken) {
5356
- headers.set('accesstoken', accessToken);
5357
- headers.set('idtoken', idToken);
5358
- }
5359
- return headers;
5360
- },
5361
- credentials: 'include',
5362
- });
5363
- };
5364
- const authDataBaseQuery = createAuthBaseQuery();
5365
5333
  /**
5366
- * Mutex to prevent multiple concurrent refresh attempts
5334
+ * Mutex to prevent multiple concurrent refresh attempts.
5335
+ * Shared across all APIs so only one refresh happens at a time.
5367
5336
  */
5368
5337
  let isRefreshing = false;
5369
5338
  let refreshPromise = null;
5370
5339
  /**
5371
- * Attempt to refresh the auth token using the refresh token from Redux state.
5340
+ * Read the refresh token from localStorage (redux-persist) as a fallback
5341
+ * when the Redux store is empty (e.g. during rehydration race).
5342
+ */
5343
+ function getRefreshTokenFromPersist() {
5344
+ if (typeof localStorage === 'undefined')
5345
+ return undefined;
5346
+ try {
5347
+ const raw = localStorage.getItem('persist:root');
5348
+ if (!raw)
5349
+ return undefined;
5350
+ const parsed = JSON.parse(raw);
5351
+ const auth = typeof parsed.auth === 'string' ? JSON.parse(parsed.auth) : parsed.auth;
5352
+ return auth?.user?.refreshToken || undefined;
5353
+ }
5354
+ catch {
5355
+ return undefined;
5356
+ }
5357
+ }
5358
+ /**
5359
+ * Attempt to refresh the auth token using the refresh token from Redux state,
5360
+ * falling back to localStorage if Redux is empty (rehydration race).
5372
5361
  * Returns true if refresh succeeded and tokens were updated.
5373
5362
  */
5374
5363
  async function attemptTokenRefresh(api) {
5375
5364
  const state = api.getState();
5376
- const refreshToken = state?.auth?.user?.refreshToken;
5365
+ let refreshToken = state?.auth?.user?.refreshToken;
5366
+ // Fallback: if Redux store is empty (rehydration hasn't completed), read from localStorage
5367
+ if (!refreshToken) {
5368
+ refreshToken = getRefreshTokenFromPersist();
5369
+ if (refreshToken) {
5370
+ console.debug('[TokenRefresh] Redux empty, recovered refresh token from localStorage');
5371
+ }
5372
+ }
5377
5373
  console.debug('[TokenRefresh] Attempting refresh', {
5378
5374
  hasRefreshToken: !!refreshToken,
5379
- refreshTokenPreview: refreshToken ? `${refreshToken.substring(0, 20)}...` : 'none',
5375
+ source: state?.auth?.user?.refreshToken ? 'redux' : (refreshToken ? 'localStorage' : 'none'),
5380
5376
  });
5381
5377
  if (!refreshToken) {
5382
- console.warn('[TokenRefresh] No refresh token in Redux store — cannot refresh');
5378
+ console.warn('[TokenRefresh] No refresh token in Redux or localStorage — cannot refresh');
5383
5379
  return false;
5384
5380
  }
5385
5381
  // Use the Next.js API route for refresh (proxies to auth microservice)
@@ -5451,46 +5447,86 @@ async function attemptTokenRefresh(api) {
5451
5447
  }
5452
5448
  }
5453
5449
  /**
5454
- * This function retries the request after refreshing the token on 401.
5455
- * If the refresh fails, it dispatches logout.
5450
+ * Wraps any base query with 401 reauth logic.
5451
+ * On 401, attempts to refresh the token and retry the original request.
5452
+ * If refresh fails, dispatches logout.
5453
+ *
5454
+ * Uses a shared mutex so concurrent 401s across different APIs
5455
+ * only trigger one refresh.
5456
5456
  */
5457
- const authDataBaseQueryWithReauth = async (args, api, extraOptions) => {
5458
- let result = await authDataBaseQuery(args, api, extraOptions);
5459
- if (result?.error) {
5460
- const requestUrl = typeof args === 'string' ? args : args?.url;
5461
- console.debug('[AuthQuery] Request error', {
5462
- url: requestUrl,
5463
- status: result.error.status,
5464
- data: result.error?.data,
5465
- });
5466
- }
5467
- if (result?.error?.status === 401) {
5468
- const requestUrl = typeof args === 'string' ? args : args?.url;
5469
- console.warn('[TokenRefresh] 401 received, attempting token refresh', {
5470
- url: requestUrl,
5471
- errorStatus: result.error.status,
5472
- isAlreadyRefreshing: isRefreshing,
5473
- });
5474
- // Use mutex to prevent concurrent refreshes
5475
- if (!isRefreshing) {
5476
- isRefreshing = true;
5477
- refreshPromise = attemptTokenRefresh(api).finally(() => {
5478
- isRefreshing = false;
5479
- refreshPromise = null;
5457
+ function withReauth(baseQuery) {
5458
+ return async (args, api, extraOptions) => {
5459
+ let result = await baseQuery(args, api, extraOptions);
5460
+ if (result?.error) {
5461
+ const requestUrl = typeof args === 'string' ? args : args?.url;
5462
+ console.debug('[AuthQuery] Request error', {
5463
+ url: requestUrl,
5464
+ status: result.error.status,
5465
+ data: result.error?.data,
5480
5466
  });
5481
5467
  }
5482
- const refreshed = await (refreshPromise || attemptTokenRefresh(api));
5483
- if (refreshed) {
5484
- console.debug('[TokenRefresh] Refresh succeeded retrying original request');
5485
- result = await authDataBaseQuery(args, api, extraOptions);
5486
- }
5487
- else {
5488
- console.error('[TokenRefresh] Refresh FAILED — dispatching logout');
5489
- api.dispatch(slice.logout());
5468
+ if (result?.error?.status === 401) {
5469
+ const requestUrl = typeof args === 'string' ? args : args?.url;
5470
+ console.warn('[TokenRefresh] 401 received, attempting token refresh', {
5471
+ url: requestUrl,
5472
+ errorStatus: result.error.status,
5473
+ isAlreadyRefreshing: isRefreshing,
5474
+ });
5475
+ // Use mutex to prevent concurrent refreshes across all APIs
5476
+ if (!isRefreshing) {
5477
+ isRefreshing = true;
5478
+ refreshPromise = attemptTokenRefresh(api).finally(() => {
5479
+ isRefreshing = false;
5480
+ });
5481
+ }
5482
+ const refreshed = await refreshPromise;
5483
+ // Safe to clear after all concurrent waiters have resolved
5484
+ refreshPromise = null;
5485
+ if (refreshed) {
5486
+ console.debug('[TokenRefresh] Refresh succeeded — retrying original request');
5487
+ result = await baseQuery(args, api, extraOptions);
5488
+ }
5489
+ else {
5490
+ console.error('[TokenRefresh] Refresh FAILED — dispatching logout');
5491
+ api.dispatch(slice.logout());
5492
+ }
5490
5493
  }
5491
- }
5492
- return result;
5494
+ return result;
5495
+ };
5496
+ }
5497
+
5498
+ /**
5499
+ * Shared helper to extract auth tokens from the Redux store.
5500
+ * Used by all RTK Query services to attach tokens to request headers
5501
+ * without depending on aws-amplify's Auth.currentSession().
5502
+ */
5503
+ function getAuthHeaders(getState) {
5504
+ const state = getState();
5505
+ const accessToken = state?.auth?.user?.accessToken;
5506
+ const idToken = state?.auth?.user?.idToken;
5507
+ return { accessToken, idToken };
5508
+ }
5509
+
5510
+ // Create a dynamic baseQuery that resolves URL at request time and unwraps standard responses
5511
+ const createAuthBaseQuery = () => {
5512
+ const baseUrl = typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_API_AUTH_PREFIX
5513
+ ? process.env.NEXT_PUBLIC_API_AUTH_PREFIX
5514
+ : (API_AUTH_PREFIX || '');
5515
+ return createUnwrappingBaseQuery({
5516
+ baseUrl,
5517
+ prepareHeaders: (headers, { getState }) => {
5518
+ headers.set('Content-Type', 'application/json');
5519
+ const { accessToken, idToken } = getAuthHeaders(getState);
5520
+ if (accessToken && idToken) {
5521
+ headers.set('accesstoken', accessToken);
5522
+ headers.set('idtoken', idToken);
5523
+ }
5524
+ return headers;
5525
+ },
5526
+ credentials: 'include',
5527
+ });
5493
5528
  };
5529
+ const authDataBaseQueryWithReauth = withReauth(createAuthBaseQuery());
5494
5530
  const authApi = createApi({
5495
5531
  reducerPath: 'authApi',
5496
5532
  baseQuery: authDataBaseQueryWithReauth,
@@ -5648,6 +5684,14 @@ const authApi = createApi({
5648
5684
  }),
5649
5685
  providesTags: ['WeeklyProgress'],
5650
5686
  }),
5687
+ setWeeklyGoal: builder.mutation({
5688
+ query: ({ sub, goal }) => ({
5689
+ url: '/user/weekly-goal',
5690
+ method: 'PUT',
5691
+ body: { sub, goal },
5692
+ }),
5693
+ invalidatesTags: ['WeeklyProgress', 'UserData'],
5694
+ }),
5651
5695
  getWatchProgress: builder.query({
5652
5696
  query: ({ sub, limit = 1 }) => ({
5653
5697
  url: `/user/watch-progress?sub=${sub}&limit=${limit}`,
@@ -5718,7 +5762,7 @@ const {
5718
5762
  // useLogoutQuery,
5719
5763
  // useLazyLogoutQuery,
5720
5764
  useResetPasswordMutation, // Use this for mobile app.
5721
- useResetPasswordAuthMutation, useRegisterMutation, useVerifyUserQuery, useLazyVerifyUserQuery, useGetUserInfoQuery, useLazyGetUserInfoQuery, useUpdateUserInfoMutation, useForgottenPasswordMutation, useVerifyUserAttributesQuery, useLazyVerifyUserAttributesQuery, useVerifyUserResendQuery, useLazyVerifyUserResendQuery, useUpdateUserMutation, useGetStreakQuery, useLazyGetStreakQuery, useGetWeeklyProgressQuery, useLazyGetWeeklyProgressQuery, useGetWatchProgressQuery, useLazyGetWatchProgressQuery, useGetRecommendationsQuery, useLazyGetRecommendationsQuery, useGetActivityStatsQuery, useLazyGetActivityStatsQuery, useLogActivityMutation, useGetActiveChallengesQuery, useGetChallengeProgressQuery, } = authApi;
5765
+ useResetPasswordAuthMutation, useRegisterMutation, useVerifyUserQuery, useLazyVerifyUserQuery, useGetUserInfoQuery, useLazyGetUserInfoQuery, useUpdateUserInfoMutation, useForgottenPasswordMutation, useVerifyUserAttributesQuery, useLazyVerifyUserAttributesQuery, useVerifyUserResendQuery, useLazyVerifyUserResendQuery, useUpdateUserMutation, useGetStreakQuery, useLazyGetStreakQuery, useGetWeeklyProgressQuery, useLazyGetWeeklyProgressQuery, useSetWeeklyGoalMutation, useGetWatchProgressQuery, useLazyGetWatchProgressQuery, useGetRecommendationsQuery, useLazyGetRecommendationsQuery, useGetActivityStatsQuery, useLazyGetActivityStatsQuery, useLogActivityMutation, useGetActiveChallengesQuery, useGetChallengeProgressQuery, } = authApi;
5722
5766
 
5723
5767
  // Create dynamic baseQuery that resolves URL at request time
5724
5768
  const createContentBaseQuery = () => {
@@ -5783,8 +5827,8 @@ const contentApi = createApi({
5783
5827
  });
5784
5828
  const { useGetDataQuery, useLazyGetDataQuery, useGetDataByIdQuery, useLazyGetDataByIdQuery, } = contentApi;
5785
5829
 
5786
- // Dynamic baseQuery that resolves URL at request time and unwraps standard responses
5787
- const dynamicBaseQuery$1 = async (args, api, extraOptions) => {
5830
+ // Dynamic baseQuery that resolves URL at request time, unwraps responses, and handles 401 reauth
5831
+ const dynamicBaseQuery$1 = withReauth(async (args, api, extraOptions) => {
5788
5832
  // Resolve base URL at request time, not module load time
5789
5833
  const baseUrl = typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_API_PAYMENTS_PREFIX
5790
5834
  ? process.env.NEXT_PUBLIC_API_PAYMENTS_PREFIX
@@ -5803,7 +5847,7 @@ const dynamicBaseQuery$1 = async (args, api, extraOptions) => {
5803
5847
  credentials: 'include',
5804
5848
  });
5805
5849
  return baseQuery(args, api, extraOptions);
5806
- };
5850
+ });
5807
5851
  const paymentApi = createApi({
5808
5852
  reducerPath: 'paymentApi',
5809
5853
  baseQuery: dynamicBaseQuery$1,
@@ -5848,8 +5892,8 @@ const paymentApi = createApi({
5848
5892
  // Export hooks for usage in functional components.
5849
5893
  const { useCheckUserSubscriptionQuery, useLazyCheckUserSubscriptionQuery, useGetPaymentPlansQuery, useLazyGetPaymentPlansQuery, useGetTaxRatesQuery, useLazyGetTaxRatesQuery, useCheckPromoCodeQuery, useLazyCheckPromoCodeQuery, } = paymentApi;
5850
5894
 
5851
- // Dynamic baseQuery that resolves URL at request time and unwraps standard responses
5852
- const dynamicBaseQuery = async (args, api, extraOptions) => {
5895
+ // Dynamic baseQuery that resolves URL at request time, unwraps responses, and handles 401 reauth
5896
+ const dynamicBaseQuery = withReauth(async (args, api, extraOptions) => {
5853
5897
  // Resolve base URL at request time, not module load time
5854
5898
  const baseUrl = typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_API_PAYMENTS_PREFIX
5855
5899
  ? process.env.NEXT_PUBLIC_API_PAYMENTS_PREFIX
@@ -5868,7 +5912,7 @@ const dynamicBaseQuery = async (args, api, extraOptions) => {
5868
5912
  credentials: 'include',
5869
5913
  });
5870
5914
  return baseQuery(args, api, extraOptions);
5871
- };
5915
+ });
5872
5916
  const productsApi = createApi({
5873
5917
  reducerPath: 'productsApi',
5874
5918
  baseQuery: dynamicBaseQuery,
@@ -5925,17 +5969,7 @@ const createOrdersBaseQuery = () => {
5925
5969
  credentials: 'include',
5926
5970
  });
5927
5971
  };
5928
- const dataBaseQuery = createOrdersBaseQuery();
5929
- /**
5930
- * This function is used to retry a request if we get a 401 error.
5931
- */
5932
- const dataBaseQueryWithReauth = async (args, api, extraOptions) => {
5933
- const result = await dataBaseQuery(args, api, extraOptions);
5934
- if (result?.error?.status === 401) {
5935
- api.dispatch(slice.logout());
5936
- }
5937
- return result;
5938
- };
5972
+ const dataBaseQueryWithReauth = withReauth(createOrdersBaseQuery());
5939
5973
  const ordersApi = createApi({
5940
5974
  reducerPath: 'ordersApi',
5941
5975
  baseQuery: dataBaseQueryWithReauth,
@@ -5958,9 +5992,12 @@ const ordersApi = createApi({
5958
5992
  * Get all partnership orders for a customer from Stripe.
5959
5993
  */
5960
5994
  getOrders: builder.query({
5961
- query: ({ customerId }) => ({
5995
+ query: ({ customerId, email }) => ({
5962
5996
  url: `/payment/orders`,
5963
- params: { customerId },
5997
+ params: {
5998
+ ...(customerId && { customerId }),
5999
+ ...(email && { email }),
6000
+ },
5964
6001
  }),
5965
6002
  providesTags: ['Orders'],
5966
6003
  }),
@@ -6229,9 +6266,11 @@ exports.usePurchaseProductMutation = usePurchaseProductMutation;
6229
6266
  exports.useRegisterMutation = useRegisterMutation;
6230
6267
  exports.useResetPasswordAuthMutation = useResetPasswordAuthMutation;
6231
6268
  exports.useResetPasswordMutation = useResetPasswordMutation;
6269
+ exports.useSetWeeklyGoalMutation = useSetWeeklyGoalMutation;
6232
6270
  exports.useUpdateUserInfoMutation = useUpdateUserInfoMutation;
6233
6271
  exports.useUpdateUserMutation = useUpdateUserMutation;
6234
6272
  exports.useVerifyUserAttributesQuery = useVerifyUserAttributesQuery;
6235
6273
  exports.useVerifyUserQuery = useVerifyUserQuery;
6236
6274
  exports.useVerifyUserResendQuery = useVerifyUserResendQuery;
6237
- //# sourceMappingURL=slice-C4MKCa5N.js.map
6275
+ exports.withReauth = withReauth;
6276
+ //# sourceMappingURL=slice-BZ9cHcE8.js.map