@mitumba/sdk 1.11.0 → 1.13.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;
@@ -931,6 +1067,46 @@ var BusinessesModule = class {
931
1067
  }
932
1068
  };
933
1069
 
1070
+ // src/modules/admin.ts
1071
+ var AdminModule = class {
1072
+ constructor(client) {
1073
+ this.client = client;
1074
+ }
1075
+ client;
1076
+ /** Block an IP address. Default duration: 24 hours. */
1077
+ async blockIp(ip, durationHours, options) {
1078
+ return this.client.post("/admin/block-ip", { ip, duration_hours: durationHours ?? 24 }, options);
1079
+ }
1080
+ /** Unblock an IP address. */
1081
+ async unblockIp(ip, options) {
1082
+ return this.client.post("/admin/unblock-ip", { ip }, options);
1083
+ }
1084
+ /** List currently blocked IPs. */
1085
+ async getBlockedIps(options) {
1086
+ return this.client.get("/admin/blocked-ips", void 0, options);
1087
+ }
1088
+ /** Get platform-wide statistics. */
1089
+ async getSystemStats(options) {
1090
+ return this.client.get("/admin/stats", void 0, options);
1091
+ }
1092
+ /** Suspend a user account. */
1093
+ async suspendUser(userId, reason, options) {
1094
+ return this.client.post(`/admin/users/${userId}/suspend`, { reason }, options);
1095
+ }
1096
+ /** Unsuspend a user account. */
1097
+ async unsuspendUser(userId, options) {
1098
+ return this.client.post(`/admin/users/${userId}/unsuspend`, void 0, options);
1099
+ }
1100
+ /** Grant verified badge to a store. */
1101
+ async verifyStore(storeId, options) {
1102
+ return this.client.post(`/admin/stores/${storeId}/verify`, void 0, options);
1103
+ }
1104
+ /** Revoke verified badge from a store. */
1105
+ async unverifyStore(storeId, options) {
1106
+ return this.client.post(`/admin/stores/${storeId}/unverify`, void 0, options);
1107
+ }
1108
+ };
1109
+
934
1110
  // src/types/listings.ts
935
1111
  var CONDITIONS = ["new", "like_new", "good", "fair"];
936
1112
  var LISTING_STATUSES = ["draft", "active", "sold", "removed"];
@@ -969,6 +1145,7 @@ var MitumbaClient = class {
969
1145
  settings;
970
1146
  mailer;
971
1147
  businesses;
1148
+ admin;
972
1149
  constructor(config) {
973
1150
  this.api = new APIClient(config);
974
1151
  this.auth = new AuthModule(this.api);
@@ -986,6 +1163,7 @@ var MitumbaClient = class {
986
1163
  this.settings = new SettingsModule(this.api);
987
1164
  this.mailer = new MailerModule(this.api);
988
1165
  this.businesses = new BusinessesModule(this.api);
1166
+ this.admin = new AdminModule(this.api);
989
1167
  }
990
1168
  /**
991
1169
  * Set the access token for authenticated requests.
@@ -1006,8 +1184,20 @@ var MitumbaClient = class {
1006
1184
  clearToken() {
1007
1185
  this.api.clearToken();
1008
1186
  }
1187
+ /**
1188
+ * Hydrate tokens from persistent store (call on app boot).
1189
+ */
1190
+ hydrate() {
1191
+ return this.api.hydrate();
1192
+ }
1193
+ /**
1194
+ * Check if user has a stored session.
1195
+ */
1196
+ isAuthenticated() {
1197
+ return this.api.isAuthenticated();
1198
+ }
1009
1199
  };
1010
1200
 
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 };
1201
+ export { APIClient, APIError, AdminModule, 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
1202
  //# sourceMappingURL=index.mjs.map
1013
1203
  //# sourceMappingURL=index.mjs.map