@mitumba/sdk 1.10.0 → 1.12.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/dist/index.d.mts CHANGED
@@ -1,3 +1,29 @@
1
+ interface TokenStore {
2
+ getAccessToken(): Promise<string | null>;
3
+ getRefreshToken(): Promise<string | null>;
4
+ setTokens(access: string, refresh: string): Promise<void>;
5
+ clear(): Promise<void>;
6
+ }
7
+ declare class MemoryTokenStore implements TokenStore {
8
+ private accessToken;
9
+ private refreshToken;
10
+ getAccessToken(): Promise<string | null>;
11
+ getRefreshToken(): Promise<string | null>;
12
+ setTokens(access: string, refresh: string): Promise<void>;
13
+ clear(): Promise<void>;
14
+ }
15
+ declare class IDBTokenStore implements TokenStore {
16
+ private dbPromise;
17
+ private openDB;
18
+ private get;
19
+ getAccessToken(): Promise<string | null>;
20
+ getRefreshToken(): Promise<string | null>;
21
+ setTokens(access: string, refresh: string): Promise<void>;
22
+ clear(): Promise<void>;
23
+ }
24
+ /** Creates IDBTokenStore if indexedDB is available, otherwise MemoryTokenStore. */
25
+ declare function createTokenStore(): TokenStore;
26
+
1
27
  interface AuthTokens {
2
28
  access_token: string;
3
29
  refresh_token: string;
@@ -649,6 +675,16 @@ interface MitumbaClientConfig {
649
675
  token: string;
650
676
  refreshToken: string;
651
677
  }) => void;
678
+ /**
679
+ * Callback invoked when the refresh token is invalid/expired and the user
680
+ * needs to re-authenticate. Only fires when session is truly dead.
681
+ */
682
+ onAuthExpired?: () => void;
683
+ /**
684
+ * Custom token store for persisting tokens. Defaults to IndexedDB in browsers,
685
+ * in-memory elsewhere. Pass your own implementation for custom persistence.
686
+ */
687
+ tokenStore?: TokenStore;
652
688
  }
653
689
  interface APIErrorResponse {
654
690
  error: string;
@@ -675,13 +711,23 @@ declare class APIError extends Error {
675
711
  }
676
712
  declare class APIClient {
677
713
  private config;
714
+ private tokenStore;
678
715
  private isRefreshing;
679
716
  private refreshPromise;
680
717
  constructor(config: MitumbaClientConfig);
681
- setToken(token: string, refreshToken?: string): void;
718
+ setToken(token: string, refreshToken?: string): Promise<void>;
682
719
  getToken(): string | undefined;
683
720
  getBaseUrl(): string;
684
- clearToken(): void;
721
+ clearToken(): Promise<void>;
722
+ /**
723
+ * Check if user has a stored session (access or refresh token available).
724
+ */
725
+ isAuthenticated(): Promise<boolean>;
726
+ /**
727
+ * Hydrate tokens from the store into memory (call on app boot).
728
+ */
729
+ hydrate(): Promise<void>;
730
+ private ensureFreshToken;
685
731
  private request;
686
732
  private handleTokenRefresh;
687
733
  get<T>(path: string, params?: Record<string, string | number | boolean | undefined>, options?: RequestOptions): Promise<T>;
@@ -1200,6 +1246,13 @@ declare class CartModule {
1200
1246
  remove(listingId: string, options?: RequestOptions): Promise<{
1201
1247
  ok: true;
1202
1248
  }>;
1249
+ /**
1250
+ * Update the quantity of a cart item.
1251
+ */
1252
+ updateQuantity(listingId: string, quantity: number, options?: RequestOptions): Promise<{
1253
+ ok: true;
1254
+ quantity: number;
1255
+ }>;
1203
1256
  /**
1204
1257
  * Checkout the cart — creates orders grouped by store and clears the cart.
1205
1258
  */
@@ -1343,6 +1396,14 @@ declare class MitumbaClient {
1343
1396
  * Clear the current tokens.
1344
1397
  */
1345
1398
  clearToken(): void;
1399
+ /**
1400
+ * Hydrate tokens from persistent store (call on app boot).
1401
+ */
1402
+ hydrate(): Promise<void>;
1403
+ /**
1404
+ * Check if user has a stored session.
1405
+ */
1406
+ isAuthenticated(): Promise<boolean>;
1346
1407
  }
1347
1408
 
1348
- export { APIClient, APIError, type APIErrorResponse, type AddAddressInput, type AddPaymentMethodInput, type Address, AuthModule, type AuthTokens, type BecomeSellerInput, type Business, BusinessesModule, CONDITIONS, type CartItem, CartModule, type Category, type ChangePasswordInput, type City, type CompleteOnboardingInput, type Condition, type Conversation, type CreateListingInput, type CreateOrderInput, type CreateReviewInput, type CreateStoreInput, type DeliveryAddress, type EmailLoginInput, type EmailRegisterInput, type ForgotPasswordInput, LISTING_STATUSES, type LinkedAccount, type LinkedAccountProvider, type Listing, type ListingImage, type ListingStatus, type ListingsFeedParams, ListingsModule, type LoginInput, MailerModule, type MailerTemplate, type Message, type MessageResponse, MessagesModule, MitumbaClient, type MitumbaClientConfig, type MpesaInput, type Notification, type NotificationPref, type NotificationType, NotificationsModule, ORDER_STATUSES, type Order, type OrderEvent, type OrderHistoryParams, type OrderStatus, OrdersModule, PAYMENT_STATUSES, type PaginatedResponse, PayModule, type PaymentMethod, type PaymentMethodType, type PaymentStatus, type PaymentStatusResponse, type PaystackInitResponse, type PaystackInput, type PhoneLoginInput, type PhoneRegisterInput, type PresignImageResponse, type RegisterInput, type RequestOptions, type ResetPasswordInput, type Review, type ReviewableOrder, ReviewsModule, type SaveSearchInput, type SearchHistoryItem, SearchModule, type SearchParams, type SearchResult, type SellerProfile, type SellerStorefront, type SendEmailInput, type SendMessageInput, type SendOtpInput, type Session, SettingsModule, type SimilarListing, type StkPushInput, type StkPushResponse, type Store, type StoreAnalytics, type StoreSettings, type StoreStats, StoresModule, type SubscriptionTier, type TransitionOrderInput, type TrendingTerm, type TwoFactorRequired, type UpdateBusinessInput, type UpdateListingInput, type UpdateProfileInput, type UpdateStoreInput, type UserProfile, type VAZIOutfit, type VAZIOutfitDetail, type VAZIOutfitDetailItem, type VAZIOutfitItem, type VaziFeedParams, type VaziFeedResponse, VaziModule, type Verify2FAInput, type VerifyOtpInput, type WishlistListing, WishlistsModule };
1409
+ export { APIClient, APIError, type APIErrorResponse, type AddAddressInput, type AddPaymentMethodInput, type Address, AuthModule, type AuthTokens, type BecomeSellerInput, type Business, BusinessesModule, CONDITIONS, type CartItem, CartModule, type Category, type ChangePasswordInput, type City, type CompleteOnboardingInput, type Condition, type Conversation, type CreateListingInput, type CreateOrderInput, type CreateReviewInput, type CreateStoreInput, type DeliveryAddress, type EmailLoginInput, type EmailRegisterInput, type ForgotPasswordInput, IDBTokenStore, LISTING_STATUSES, type LinkedAccount, type LinkedAccountProvider, type Listing, type ListingImage, type ListingStatus, type ListingsFeedParams, ListingsModule, type LoginInput, MailerModule, type MailerTemplate, MemoryTokenStore, type Message, type MessageResponse, MessagesModule, MitumbaClient, type MitumbaClientConfig, type MpesaInput, type Notification, type NotificationPref, type NotificationType, NotificationsModule, ORDER_STATUSES, type Order, type OrderEvent, type OrderHistoryParams, type OrderStatus, OrdersModule, PAYMENT_STATUSES, type PaginatedResponse, PayModule, type PaymentMethod, type PaymentMethodType, type PaymentStatus, type PaymentStatusResponse, type PaystackInitResponse, type PaystackInput, type PhoneLoginInput, type PhoneRegisterInput, type PresignImageResponse, type RegisterInput, type RequestOptions, type ResetPasswordInput, type Review, type ReviewableOrder, ReviewsModule, type SaveSearchInput, type SearchHistoryItem, SearchModule, type SearchParams, type SearchResult, type SellerProfile, type SellerStorefront, type SendEmailInput, type SendMessageInput, type SendOtpInput, type Session, SettingsModule, type SimilarListing, type StkPushInput, type StkPushResponse, type Store, type StoreAnalytics, type StoreSettings, type StoreStats, StoresModule, type SubscriptionTier, type TokenStore, type TransitionOrderInput, type TrendingTerm, type TwoFactorRequired, type UpdateBusinessInput, type UpdateListingInput, type UpdateProfileInput, type UpdateStoreInput, type UserProfile, type VAZIOutfit, type VAZIOutfitDetail, type VAZIOutfitDetailItem, type VAZIOutfitItem, type VaziFeedParams, type VaziFeedResponse, VaziModule, type Verify2FAInput, type VerifyOtpInput, type WishlistListing, WishlistsModule, createTokenStore };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,29 @@
1
+ interface TokenStore {
2
+ getAccessToken(): Promise<string | null>;
3
+ getRefreshToken(): Promise<string | null>;
4
+ setTokens(access: string, refresh: string): Promise<void>;
5
+ clear(): Promise<void>;
6
+ }
7
+ declare class MemoryTokenStore implements TokenStore {
8
+ private accessToken;
9
+ private refreshToken;
10
+ getAccessToken(): Promise<string | null>;
11
+ getRefreshToken(): Promise<string | null>;
12
+ setTokens(access: string, refresh: string): Promise<void>;
13
+ clear(): Promise<void>;
14
+ }
15
+ declare class IDBTokenStore implements TokenStore {
16
+ private dbPromise;
17
+ private openDB;
18
+ private get;
19
+ getAccessToken(): Promise<string | null>;
20
+ getRefreshToken(): Promise<string | null>;
21
+ setTokens(access: string, refresh: string): Promise<void>;
22
+ clear(): Promise<void>;
23
+ }
24
+ /** Creates IDBTokenStore if indexedDB is available, otherwise MemoryTokenStore. */
25
+ declare function createTokenStore(): TokenStore;
26
+
1
27
  interface AuthTokens {
2
28
  access_token: string;
3
29
  refresh_token: string;
@@ -649,6 +675,16 @@ interface MitumbaClientConfig {
649
675
  token: string;
650
676
  refreshToken: string;
651
677
  }) => void;
678
+ /**
679
+ * Callback invoked when the refresh token is invalid/expired and the user
680
+ * needs to re-authenticate. Only fires when session is truly dead.
681
+ */
682
+ onAuthExpired?: () => void;
683
+ /**
684
+ * Custom token store for persisting tokens. Defaults to IndexedDB in browsers,
685
+ * in-memory elsewhere. Pass your own implementation for custom persistence.
686
+ */
687
+ tokenStore?: TokenStore;
652
688
  }
653
689
  interface APIErrorResponse {
654
690
  error: string;
@@ -675,13 +711,23 @@ declare class APIError extends Error {
675
711
  }
676
712
  declare class APIClient {
677
713
  private config;
714
+ private tokenStore;
678
715
  private isRefreshing;
679
716
  private refreshPromise;
680
717
  constructor(config: MitumbaClientConfig);
681
- setToken(token: string, refreshToken?: string): void;
718
+ setToken(token: string, refreshToken?: string): Promise<void>;
682
719
  getToken(): string | undefined;
683
720
  getBaseUrl(): string;
684
- clearToken(): void;
721
+ clearToken(): Promise<void>;
722
+ /**
723
+ * Check if user has a stored session (access or refresh token available).
724
+ */
725
+ isAuthenticated(): Promise<boolean>;
726
+ /**
727
+ * Hydrate tokens from the store into memory (call on app boot).
728
+ */
729
+ hydrate(): Promise<void>;
730
+ private ensureFreshToken;
685
731
  private request;
686
732
  private handleTokenRefresh;
687
733
  get<T>(path: string, params?: Record<string, string | number | boolean | undefined>, options?: RequestOptions): Promise<T>;
@@ -1200,6 +1246,13 @@ declare class CartModule {
1200
1246
  remove(listingId: string, options?: RequestOptions): Promise<{
1201
1247
  ok: true;
1202
1248
  }>;
1249
+ /**
1250
+ * Update the quantity of a cart item.
1251
+ */
1252
+ updateQuantity(listingId: string, quantity: number, options?: RequestOptions): Promise<{
1253
+ ok: true;
1254
+ quantity: number;
1255
+ }>;
1203
1256
  /**
1204
1257
  * Checkout the cart — creates orders grouped by store and clears the cart.
1205
1258
  */
@@ -1343,6 +1396,14 @@ declare class MitumbaClient {
1343
1396
  * Clear the current tokens.
1344
1397
  */
1345
1398
  clearToken(): void;
1399
+ /**
1400
+ * Hydrate tokens from persistent store (call on app boot).
1401
+ */
1402
+ hydrate(): Promise<void>;
1403
+ /**
1404
+ * Check if user has a stored session.
1405
+ */
1406
+ isAuthenticated(): Promise<boolean>;
1346
1407
  }
1347
1408
 
1348
- export { APIClient, APIError, type APIErrorResponse, type AddAddressInput, type AddPaymentMethodInput, type Address, AuthModule, type AuthTokens, type BecomeSellerInput, type Business, BusinessesModule, CONDITIONS, type CartItem, CartModule, type Category, type ChangePasswordInput, type City, type CompleteOnboardingInput, type Condition, type Conversation, type CreateListingInput, type CreateOrderInput, type CreateReviewInput, type CreateStoreInput, type DeliveryAddress, type EmailLoginInput, type EmailRegisterInput, type ForgotPasswordInput, LISTING_STATUSES, type LinkedAccount, type LinkedAccountProvider, type Listing, type ListingImage, type ListingStatus, type ListingsFeedParams, ListingsModule, type LoginInput, MailerModule, type MailerTemplate, type Message, type MessageResponse, MessagesModule, MitumbaClient, type MitumbaClientConfig, type MpesaInput, type Notification, type NotificationPref, type NotificationType, NotificationsModule, ORDER_STATUSES, type Order, type OrderEvent, type OrderHistoryParams, type OrderStatus, OrdersModule, PAYMENT_STATUSES, type PaginatedResponse, PayModule, type PaymentMethod, type PaymentMethodType, type PaymentStatus, type PaymentStatusResponse, type PaystackInitResponse, type PaystackInput, type PhoneLoginInput, type PhoneRegisterInput, type PresignImageResponse, type RegisterInput, type RequestOptions, type ResetPasswordInput, type Review, type ReviewableOrder, ReviewsModule, type SaveSearchInput, type SearchHistoryItem, SearchModule, type SearchParams, type SearchResult, type SellerProfile, type SellerStorefront, type SendEmailInput, type SendMessageInput, type SendOtpInput, type Session, SettingsModule, type SimilarListing, type StkPushInput, type StkPushResponse, type Store, type StoreAnalytics, type StoreSettings, type StoreStats, StoresModule, type SubscriptionTier, type TransitionOrderInput, type TrendingTerm, type TwoFactorRequired, type UpdateBusinessInput, type UpdateListingInput, type UpdateProfileInput, type UpdateStoreInput, type UserProfile, type VAZIOutfit, type VAZIOutfitDetail, type VAZIOutfitDetailItem, type VAZIOutfitItem, type VaziFeedParams, type VaziFeedResponse, VaziModule, type Verify2FAInput, type VerifyOtpInput, type WishlistListing, WishlistsModule };
1409
+ export { APIClient, APIError, type APIErrorResponse, type AddAddressInput, type AddPaymentMethodInput, type Address, AuthModule, type AuthTokens, type BecomeSellerInput, type Business, BusinessesModule, CONDITIONS, type CartItem, CartModule, type Category, type ChangePasswordInput, type City, type CompleteOnboardingInput, type Condition, type Conversation, type CreateListingInput, type CreateOrderInput, type CreateReviewInput, type CreateStoreInput, type DeliveryAddress, type EmailLoginInput, type EmailRegisterInput, type ForgotPasswordInput, IDBTokenStore, LISTING_STATUSES, type LinkedAccount, type LinkedAccountProvider, type Listing, type ListingImage, type ListingStatus, type ListingsFeedParams, ListingsModule, type LoginInput, MailerModule, type MailerTemplate, MemoryTokenStore, type Message, type MessageResponse, MessagesModule, MitumbaClient, type MitumbaClientConfig, type MpesaInput, type Notification, type NotificationPref, type NotificationType, NotificationsModule, ORDER_STATUSES, type Order, type OrderEvent, type OrderHistoryParams, type OrderStatus, OrdersModule, PAYMENT_STATUSES, type PaginatedResponse, PayModule, type PaymentMethod, type PaymentMethodType, type PaymentStatus, type PaymentStatusResponse, type PaystackInitResponse, type PaystackInput, type PhoneLoginInput, type PhoneRegisterInput, type PresignImageResponse, type RegisterInput, type RequestOptions, type ResetPasswordInput, type Review, type ReviewableOrder, ReviewsModule, type SaveSearchInput, type SearchHistoryItem, SearchModule, type SearchParams, type SearchResult, type SellerProfile, type SellerStorefront, type SendEmailInput, type SendMessageInput, type SendOtpInput, type Session, SettingsModule, type SimilarListing, type StkPushInput, type StkPushResponse, type Store, type StoreAnalytics, type StoreSettings, type StoreStats, StoresModule, type SubscriptionTier, type TokenStore, type TransitionOrderInput, type TrendingTerm, type TwoFactorRequired, type UpdateBusinessInput, type UpdateListingInput, type UpdateProfileInput, type UpdateStoreInput, type UserProfile, type VAZIOutfit, type VAZIOutfitDetail, type VAZIOutfitDetailItem, type VAZIOutfitItem, type VaziFeedParams, type VaziFeedResponse, VaziModule, type Verify2FAInput, type VerifyOtpInput, type WishlistListing, WishlistsModule, createTokenStore };
package/dist/index.js CHANGED
@@ -1,5 +1,84 @@
1
1
  'use strict';
2
2
 
3
+ // src/token-store.ts
4
+ var MemoryTokenStore = class {
5
+ accessToken = null;
6
+ refreshToken = null;
7
+ async getAccessToken() {
8
+ return this.accessToken;
9
+ }
10
+ async getRefreshToken() {
11
+ return this.refreshToken;
12
+ }
13
+ async setTokens(access, refresh) {
14
+ this.accessToken = access;
15
+ this.refreshToken = refresh;
16
+ }
17
+ async clear() {
18
+ this.accessToken = null;
19
+ this.refreshToken = null;
20
+ }
21
+ };
22
+ var DB_NAME = "mitumba_sdk";
23
+ var STORE_NAME = "tokens";
24
+ var KEY = "auth";
25
+ var IDBTokenStore = class {
26
+ dbPromise = null;
27
+ openDB() {
28
+ if (this.dbPromise) return this.dbPromise;
29
+ this.dbPromise = new Promise((resolve, reject) => {
30
+ const request = indexedDB.open(DB_NAME, 1);
31
+ request.onupgradeneeded = () => {
32
+ request.result.createObjectStore(STORE_NAME);
33
+ };
34
+ request.onsuccess = () => resolve(request.result);
35
+ request.onerror = () => reject(request.error);
36
+ });
37
+ return this.dbPromise;
38
+ }
39
+ async get() {
40
+ const db = await this.openDB();
41
+ return new Promise((resolve, reject) => {
42
+ const tx = db.transaction(STORE_NAME, "readonly");
43
+ const req = tx.objectStore(STORE_NAME).get(KEY);
44
+ req.onsuccess = () => resolve(req.result || null);
45
+ req.onerror = () => reject(req.error);
46
+ });
47
+ }
48
+ async getAccessToken() {
49
+ const data = await this.get();
50
+ return data?.access ?? null;
51
+ }
52
+ async getRefreshToken() {
53
+ const data = await this.get();
54
+ return data?.refresh ?? null;
55
+ }
56
+ async setTokens(access, refresh) {
57
+ const db = await this.openDB();
58
+ return new Promise((resolve, reject) => {
59
+ const tx = db.transaction(STORE_NAME, "readwrite");
60
+ const req = tx.objectStore(STORE_NAME).put({ access, refresh }, KEY);
61
+ req.onsuccess = () => resolve();
62
+ req.onerror = () => reject(req.error);
63
+ });
64
+ }
65
+ async clear() {
66
+ const db = await this.openDB();
67
+ return new Promise((resolve, reject) => {
68
+ const tx = db.transaction(STORE_NAME, "readwrite");
69
+ const req = tx.objectStore(STORE_NAME).delete(KEY);
70
+ req.onsuccess = () => resolve();
71
+ req.onerror = () => reject(req.error);
72
+ });
73
+ }
74
+ };
75
+ function createTokenStore() {
76
+ if (typeof indexedDB !== "undefined") {
77
+ return new IDBTokenStore();
78
+ }
79
+ return new MemoryTokenStore();
80
+ }
81
+
3
82
  // src/client.ts
4
83
  var APIError = class extends Error {
5
84
  code;
@@ -13,18 +92,33 @@ var APIError = class extends Error {
13
92
  this.details = data.details;
14
93
  }
15
94
  };
95
+ function isExpiringSoon(token, thresholdSeconds) {
96
+ try {
97
+ const payload = JSON.parse(atob(token.split(".")[1]));
98
+ const exp = payload.exp;
99
+ return exp - Date.now() / 1e3 < thresholdSeconds;
100
+ } catch {
101
+ return false;
102
+ }
103
+ }
16
104
  var APIClient = class {
17
105
  config;
106
+ tokenStore;
18
107
  isRefreshing = false;
19
108
  refreshPromise = null;
20
109
  constructor(config) {
21
110
  this.config = config;
111
+ this.tokenStore = config.tokenStore ?? createTokenStore();
112
+ if (config.token) {
113
+ this.tokenStore.setTokens(config.token, config.refreshToken ?? "");
114
+ }
22
115
  }
23
- setToken(token, refreshToken) {
116
+ async setToken(token, refreshToken) {
24
117
  this.config.token = token;
25
118
  if (refreshToken) {
26
119
  this.config.refreshToken = refreshToken;
27
120
  }
121
+ await this.tokenStore.setTokens(token, refreshToken ?? this.config.refreshToken ?? "");
28
122
  }
29
123
  getToken() {
30
124
  return this.config.token;
@@ -32,11 +126,44 @@ var APIClient = class {
32
126
  getBaseUrl() {
33
127
  return this.config.baseUrl;
34
128
  }
35
- clearToken() {
129
+ async clearToken() {
36
130
  this.config.token = void 0;
37
131
  this.config.refreshToken = void 0;
132
+ await this.tokenStore.clear();
133
+ }
134
+ /**
135
+ * Check if user has a stored session (access or refresh token available).
136
+ */
137
+ async isAuthenticated() {
138
+ const access = this.config.token ?? await this.tokenStore.getAccessToken();
139
+ if (access) return true;
140
+ const refresh = await this.tokenStore.getRefreshToken();
141
+ return !!refresh;
142
+ }
143
+ /**
144
+ * Hydrate tokens from the store into memory (call on app boot).
145
+ */
146
+ async hydrate() {
147
+ const access = await this.tokenStore.getAccessToken();
148
+ const refresh = await this.tokenStore.getRefreshToken();
149
+ if (access) this.config.token = access;
150
+ if (refresh) this.config.refreshToken = refresh;
151
+ }
152
+ async ensureFreshToken() {
153
+ if (!this.config.token) {
154
+ const stored = await this.tokenStore.getAccessToken();
155
+ if (stored) this.config.token = stored;
156
+ const refresh = await this.tokenStore.getRefreshToken();
157
+ if (refresh) this.config.refreshToken = refresh;
158
+ }
159
+ if (this.config.token && isExpiringSoon(this.config.token, 60) && this.config.refreshToken) {
160
+ await this.handleTokenRefresh();
161
+ }
38
162
  }
39
163
  async request(method, path, body, params, options) {
164
+ if (!path.includes("/auth/refresh") && !path.includes("/auth/login") && !path.includes("/auth/register")) {
165
+ await this.ensureFreshToken();
166
+ }
40
167
  const url = new URL(path, this.config.baseUrl);
41
168
  if (params) {
42
169
  for (const [key, value] of Object.entries(params)) {
@@ -96,9 +223,16 @@ var APIClient = class {
96
223
  break;
97
224
  }
98
225
  if (response.status === 401 && this.config.refreshToken && !path.includes("/auth/refresh")) {
99
- await this.handleTokenRefresh();
100
- headers.set("Authorization", `Bearer ${this.config.token}`);
101
- response = await fetch(url.toString(), { ...init, headers });
226
+ try {
227
+ await this.handleTokenRefresh();
228
+ headers.set("Authorization", `Bearer ${this.config.token}`);
229
+ response = await fetch(url.toString(), { ...init, headers });
230
+ } catch {
231
+ if (this.config.onAuthExpired) {
232
+ this.config.onAuthExpired();
233
+ }
234
+ throw new APIError(401, { error: "session_expired", message: "Session expired. Please log in again." });
235
+ }
102
236
  }
103
237
  if (!response.ok) {
104
238
  let errorData;
@@ -131,13 +265,15 @@ var APIClient = class {
131
265
  throw new Error("Refresh failed");
132
266
  }
133
267
  const data = await response.json();
134
- this.setToken(data.access_token, data.refresh_token);
268
+ this.config.token = data.access_token;
269
+ this.config.refreshToken = data.refresh_token;
270
+ await this.tokenStore.setTokens(data.access_token, data.refresh_token);
135
271
  if (this.config.onTokenRefresh) {
136
272
  this.config.onTokenRefresh({ token: data.access_token, refreshToken: data.refresh_token });
137
273
  }
138
274
  } catch (err) {
139
- this.clearToken();
140
- throw new APIError(401, { error: "token_expired", message: "Session expired. Please log in again." });
275
+ await this.clearToken();
276
+ throw err;
141
277
  } finally {
142
278
  this.isRefreshing = false;
143
279
  this.refreshPromise = null;
@@ -797,6 +933,12 @@ var CartModule = class {
797
933
  async remove(listingId, options) {
798
934
  return this.client.delete(`/listings/cart/${listingId}`, options);
799
935
  }
936
+ /**
937
+ * Update the quantity of a cart item.
938
+ */
939
+ async updateQuantity(listingId, quantity, options) {
940
+ return this.client.patch(`/listings/cart/${listingId}`, { quantity }, options);
941
+ }
800
942
  /**
801
943
  * Checkout the cart — creates orders grouped by store and clears the cart.
802
944
  */
@@ -1002,6 +1144,18 @@ var MitumbaClient = class {
1002
1144
  clearToken() {
1003
1145
  this.api.clearToken();
1004
1146
  }
1147
+ /**
1148
+ * Hydrate tokens from persistent store (call on app boot).
1149
+ */
1150
+ hydrate() {
1151
+ return this.api.hydrate();
1152
+ }
1153
+ /**
1154
+ * Check if user has a stored session.
1155
+ */
1156
+ isAuthenticated() {
1157
+ return this.api.isAuthenticated();
1158
+ }
1005
1159
  };
1006
1160
 
1007
1161
  exports.APIClient = APIClient;
@@ -1010,9 +1164,11 @@ exports.AuthModule = AuthModule;
1010
1164
  exports.BusinessesModule = BusinessesModule;
1011
1165
  exports.CONDITIONS = CONDITIONS;
1012
1166
  exports.CartModule = CartModule;
1167
+ exports.IDBTokenStore = IDBTokenStore;
1013
1168
  exports.LISTING_STATUSES = LISTING_STATUSES;
1014
1169
  exports.ListingsModule = ListingsModule;
1015
1170
  exports.MailerModule = MailerModule;
1171
+ exports.MemoryTokenStore = MemoryTokenStore;
1016
1172
  exports.MessagesModule = MessagesModule;
1017
1173
  exports.MitumbaClient = MitumbaClient;
1018
1174
  exports.NotificationsModule = NotificationsModule;
@@ -1026,5 +1182,6 @@ exports.SettingsModule = SettingsModule;
1026
1182
  exports.StoresModule = StoresModule;
1027
1183
  exports.VaziModule = VaziModule;
1028
1184
  exports.WishlistsModule = WishlistsModule;
1185
+ exports.createTokenStore = createTokenStore;
1029
1186
  //# sourceMappingURL=index.js.map
1030
1187
  //# sourceMappingURL=index.js.map