@mitumba/sdk 1.11.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.mjs CHANGED
@@ -1,3 +1,82 @@
1
+ // src/token-store.ts
2
+ var MemoryTokenStore = class {
3
+ accessToken = null;
4
+ refreshToken = null;
5
+ async getAccessToken() {
6
+ return this.accessToken;
7
+ }
8
+ async getRefreshToken() {
9
+ return this.refreshToken;
10
+ }
11
+ async setTokens(access, refresh) {
12
+ this.accessToken = access;
13
+ this.refreshToken = refresh;
14
+ }
15
+ async clear() {
16
+ this.accessToken = null;
17
+ this.refreshToken = null;
18
+ }
19
+ };
20
+ var DB_NAME = "mitumba_sdk";
21
+ var STORE_NAME = "tokens";
22
+ var KEY = "auth";
23
+ var IDBTokenStore = class {
24
+ dbPromise = null;
25
+ openDB() {
26
+ if (this.dbPromise) return this.dbPromise;
27
+ this.dbPromise = new Promise((resolve, reject) => {
28
+ const request = indexedDB.open(DB_NAME, 1);
29
+ request.onupgradeneeded = () => {
30
+ request.result.createObjectStore(STORE_NAME);
31
+ };
32
+ request.onsuccess = () => resolve(request.result);
33
+ request.onerror = () => reject(request.error);
34
+ });
35
+ return this.dbPromise;
36
+ }
37
+ async get() {
38
+ const db = await this.openDB();
39
+ return new Promise((resolve, reject) => {
40
+ const tx = db.transaction(STORE_NAME, "readonly");
41
+ const req = tx.objectStore(STORE_NAME).get(KEY);
42
+ req.onsuccess = () => resolve(req.result || null);
43
+ req.onerror = () => reject(req.error);
44
+ });
45
+ }
46
+ async getAccessToken() {
47
+ const data = await this.get();
48
+ return data?.access ?? null;
49
+ }
50
+ async getRefreshToken() {
51
+ const data = await this.get();
52
+ return data?.refresh ?? null;
53
+ }
54
+ async setTokens(access, refresh) {
55
+ const db = await this.openDB();
56
+ return new Promise((resolve, reject) => {
57
+ const tx = db.transaction(STORE_NAME, "readwrite");
58
+ const req = tx.objectStore(STORE_NAME).put({ access, refresh }, KEY);
59
+ req.onsuccess = () => resolve();
60
+ req.onerror = () => reject(req.error);
61
+ });
62
+ }
63
+ async clear() {
64
+ const db = await this.openDB();
65
+ return new Promise((resolve, reject) => {
66
+ const tx = db.transaction(STORE_NAME, "readwrite");
67
+ const req = tx.objectStore(STORE_NAME).delete(KEY);
68
+ req.onsuccess = () => resolve();
69
+ req.onerror = () => reject(req.error);
70
+ });
71
+ }
72
+ };
73
+ function createTokenStore() {
74
+ if (typeof indexedDB !== "undefined") {
75
+ return new IDBTokenStore();
76
+ }
77
+ return new MemoryTokenStore();
78
+ }
79
+
1
80
  // src/client.ts
2
81
  var APIError = class extends Error {
3
82
  code;
@@ -11,18 +90,33 @@ var APIError = class extends Error {
11
90
  this.details = data.details;
12
91
  }
13
92
  };
93
+ function isExpiringSoon(token, thresholdSeconds) {
94
+ try {
95
+ const payload = JSON.parse(atob(token.split(".")[1]));
96
+ const exp = payload.exp;
97
+ return exp - Date.now() / 1e3 < thresholdSeconds;
98
+ } catch {
99
+ return false;
100
+ }
101
+ }
14
102
  var APIClient = class {
15
103
  config;
104
+ tokenStore;
16
105
  isRefreshing = false;
17
106
  refreshPromise = null;
18
107
  constructor(config) {
19
108
  this.config = config;
109
+ this.tokenStore = config.tokenStore ?? createTokenStore();
110
+ if (config.token) {
111
+ this.tokenStore.setTokens(config.token, config.refreshToken ?? "");
112
+ }
20
113
  }
21
- setToken(token, refreshToken) {
114
+ async setToken(token, refreshToken) {
22
115
  this.config.token = token;
23
116
  if (refreshToken) {
24
117
  this.config.refreshToken = refreshToken;
25
118
  }
119
+ await this.tokenStore.setTokens(token, refreshToken ?? this.config.refreshToken ?? "");
26
120
  }
27
121
  getToken() {
28
122
  return this.config.token;
@@ -30,11 +124,44 @@ var APIClient = class {
30
124
  getBaseUrl() {
31
125
  return this.config.baseUrl;
32
126
  }
33
- clearToken() {
127
+ async clearToken() {
34
128
  this.config.token = void 0;
35
129
  this.config.refreshToken = void 0;
130
+ await this.tokenStore.clear();
131
+ }
132
+ /**
133
+ * Check if user has a stored session (access or refresh token available).
134
+ */
135
+ async isAuthenticated() {
136
+ const access = this.config.token ?? await this.tokenStore.getAccessToken();
137
+ if (access) return true;
138
+ const refresh = await this.tokenStore.getRefreshToken();
139
+ return !!refresh;
140
+ }
141
+ /**
142
+ * Hydrate tokens from the store into memory (call on app boot).
143
+ */
144
+ async hydrate() {
145
+ const access = await this.tokenStore.getAccessToken();
146
+ const refresh = await this.tokenStore.getRefreshToken();
147
+ if (access) this.config.token = access;
148
+ if (refresh) this.config.refreshToken = refresh;
149
+ }
150
+ async ensureFreshToken() {
151
+ if (!this.config.token) {
152
+ const stored = await this.tokenStore.getAccessToken();
153
+ if (stored) this.config.token = stored;
154
+ const refresh = await this.tokenStore.getRefreshToken();
155
+ if (refresh) this.config.refreshToken = refresh;
156
+ }
157
+ if (this.config.token && isExpiringSoon(this.config.token, 60) && this.config.refreshToken) {
158
+ await this.handleTokenRefresh();
159
+ }
36
160
  }
37
161
  async request(method, path, body, params, options) {
162
+ if (!path.includes("/auth/refresh") && !path.includes("/auth/login") && !path.includes("/auth/register")) {
163
+ await this.ensureFreshToken();
164
+ }
38
165
  const url = new URL(path, this.config.baseUrl);
39
166
  if (params) {
40
167
  for (const [key, value] of Object.entries(params)) {
@@ -94,9 +221,16 @@ var APIClient = class {
94
221
  break;
95
222
  }
96
223
  if (response.status === 401 && this.config.refreshToken && !path.includes("/auth/refresh")) {
97
- await this.handleTokenRefresh();
98
- headers.set("Authorization", `Bearer ${this.config.token}`);
99
- response = await fetch(url.toString(), { ...init, headers });
224
+ try {
225
+ await this.handleTokenRefresh();
226
+ headers.set("Authorization", `Bearer ${this.config.token}`);
227
+ response = await fetch(url.toString(), { ...init, headers });
228
+ } catch {
229
+ if (this.config.onAuthExpired) {
230
+ this.config.onAuthExpired();
231
+ }
232
+ throw new APIError(401, { error: "session_expired", message: "Session expired. Please log in again." });
233
+ }
100
234
  }
101
235
  if (!response.ok) {
102
236
  let errorData;
@@ -129,13 +263,15 @@ var APIClient = class {
129
263
  throw new Error("Refresh failed");
130
264
  }
131
265
  const data = await response.json();
132
- this.setToken(data.access_token, data.refresh_token);
266
+ this.config.token = data.access_token;
267
+ this.config.refreshToken = data.refresh_token;
268
+ await this.tokenStore.setTokens(data.access_token, data.refresh_token);
133
269
  if (this.config.onTokenRefresh) {
134
270
  this.config.onTokenRefresh({ token: data.access_token, refreshToken: data.refresh_token });
135
271
  }
136
272
  } catch (err) {
137
- this.clearToken();
138
- throw new APIError(401, { error: "token_expired", message: "Session expired. Please log in again." });
273
+ await this.clearToken();
274
+ throw err;
139
275
  } finally {
140
276
  this.isRefreshing = false;
141
277
  this.refreshPromise = null;
@@ -1006,8 +1142,20 @@ var MitumbaClient = class {
1006
1142
  clearToken() {
1007
1143
  this.api.clearToken();
1008
1144
  }
1145
+ /**
1146
+ * Hydrate tokens from persistent store (call on app boot).
1147
+ */
1148
+ hydrate() {
1149
+ return this.api.hydrate();
1150
+ }
1151
+ /**
1152
+ * Check if user has a stored session.
1153
+ */
1154
+ isAuthenticated() {
1155
+ return this.api.isAuthenticated();
1156
+ }
1009
1157
  };
1010
1158
 
1011
- export { APIClient, APIError, AuthModule, BusinessesModule, CONDITIONS, CartModule, LISTING_STATUSES, ListingsModule, MailerModule, MessagesModule, MitumbaClient, NotificationsModule, ORDER_STATUSES, OrdersModule, PAYMENT_STATUSES, PayModule, ReviewsModule, SearchModule, SettingsModule, StoresModule, VaziModule, WishlistsModule };
1159
+ export { APIClient, APIError, AuthModule, BusinessesModule, CONDITIONS, CartModule, IDBTokenStore, LISTING_STATUSES, ListingsModule, MailerModule, MemoryTokenStore, MessagesModule, MitumbaClient, NotificationsModule, ORDER_STATUSES, OrdersModule, PAYMENT_STATUSES, PayModule, ReviewsModule, SearchModule, SettingsModule, StoresModule, VaziModule, WishlistsModule, createTokenStore };
1012
1160
  //# sourceMappingURL=index.mjs.map
1013
1161
  //# sourceMappingURL=index.mjs.map