@saas-support/react 0.3.1 → 0.4.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.js CHANGED
@@ -1,6 +1,6 @@
1
- class b extends Error {
2
- constructor(t, e, s = "unknown") {
3
- super(e), this.name = "SaaSError", this.code = t, this.domain = s;
1
+ class g extends Error {
2
+ constructor(e, t, s = "unknown") {
3
+ super(t), this.name = "SaaSError", this.code = e, this.domain = s;
4
4
  }
5
5
  get isNotFound() {
6
6
  return this.code === 404;
@@ -18,38 +18,88 @@ class b extends Error {
18
18
  return this.code === 429;
19
19
  }
20
20
  }
21
- class m {
22
- constructor(t, e) {
23
- this.baseUrl = t, this.authMode = e;
21
+ class k {
22
+ constructor(e, t) {
23
+ this.onUnauthorized = null, this.baseUrl = e, this.authMode = t;
24
24
  }
25
- async request(t, e, s, r) {
26
- const i = {
25
+ /** Register a handler that refreshes tokens and returns a new access token, or null. */
26
+ setUnauthorizedHandler(e) {
27
+ this.onUnauthorized = e;
28
+ }
29
+ async request(e, t, s, r) {
30
+ try {
31
+ return await this.doRequest(e, t, s, r);
32
+ } catch (n) {
33
+ if (n instanceof g && n.isUnauthorized && this.onUnauthorized && (r != null && r.Authorization)) {
34
+ const i = await this.onUnauthorized();
35
+ if (i)
36
+ return this.doRequest(e, t, s, {
37
+ ...r,
38
+ Authorization: `Bearer ${i}`
39
+ });
40
+ }
41
+ throw n;
42
+ }
43
+ }
44
+ async get(e, t) {
45
+ return this.request("GET", e, void 0, t);
46
+ }
47
+ async post(e, t, s) {
48
+ return this.request("POST", e, t, s);
49
+ }
50
+ async patch(e, t, s) {
51
+ return this.request("PATCH", e, t, s);
52
+ }
53
+ async del(e, t) {
54
+ return this.request("DELETE", e, void 0, t);
55
+ }
56
+ async uploadBinary(e, t, s) {
57
+ try {
58
+ return await this.doUploadBinary(e, t, s);
59
+ } catch (r) {
60
+ if (r instanceof g && r.isUnauthorized && this.onUnauthorized && (s != null && s.Authorization)) {
61
+ const n = await this.onUnauthorized();
62
+ if (n)
63
+ return this.doUploadBinary(e, t, {
64
+ ...s,
65
+ Authorization: `Bearer ${n}`
66
+ });
67
+ }
68
+ throw r;
69
+ }
70
+ }
71
+ async doUploadBinary(e, t, s) {
72
+ const r = {
73
+ "Content-Type": "application/octet-stream",
74
+ ...this.getAuthHeaders(),
75
+ ...s
76
+ }, i = await (await fetch(`${this.baseUrl}${e}`, {
77
+ method: "POST",
78
+ headers: r,
79
+ body: t
80
+ })).json();
81
+ if (i.code && i.code >= 400) {
82
+ const a = this.inferDomain(e);
83
+ throw new g(i.code, i.message || "Upload failed", a);
84
+ }
85
+ return i.data;
86
+ }
87
+ async doRequest(e, t, s, r) {
88
+ const n = {
27
89
  "Content-Type": "application/json",
28
90
  ...this.getAuthHeaders(),
29
91
  ...r
30
- }, a = await (await fetch(`${this.baseUrl}${e}`, {
31
- method: t,
32
- headers: i,
92
+ }, a = await (await fetch(`${this.baseUrl}${t}`, {
93
+ method: e,
94
+ headers: n,
33
95
  body: s ? JSON.stringify(s) : void 0
34
96
  })).json();
35
97
  if (a.code && a.code >= 400) {
36
- const d = this.inferDomain(e);
37
- throw new b(a.code, a.message || "Request failed", d);
98
+ const p = this.inferDomain(t);
99
+ throw new g(a.code, a.message || "Request failed", p);
38
100
  }
39
101
  return a.data;
40
102
  }
41
- async get(t, e) {
42
- return this.request("GET", t, void 0, e);
43
- }
44
- async post(t, e, s) {
45
- return this.request("POST", t, e, s);
46
- }
47
- async patch(t, e, s) {
48
- return this.request("PATCH", t, e, s);
49
- }
50
- async del(t, e) {
51
- return this.request("DELETE", t, void 0, e);
52
- }
53
103
  getAuthHeaders() {
54
104
  switch (this.authMode.type) {
55
105
  case "publishableKey":
@@ -60,16 +110,19 @@ class m {
60
110
  return { Authorization: `Bearer ${this.authMode.token}` };
61
111
  }
62
112
  }
63
- inferDomain(t) {
64
- return t.startsWith("/auth") ? "auth" : t.startsWith("/billing") ? "billing" : t.startsWith("/report") ? "report" : "unknown";
113
+ inferDomain(e) {
114
+ return e.startsWith("/auth") ? "auth" : e.startsWith("/billing") ? "billing" : e.startsWith("/report") ? "report" : "unknown";
65
115
  }
66
116
  }
67
- class w {
68
- constructor(t) {
69
- this.accessToken = null, this.refreshToken = null, this.refreshTimer = null, this.onRefreshNeeded = null, this.storageKey = `ss_rt_${t.slice(0, 12)}`, this.refreshToken = this.loadRefreshToken();
117
+ class b {
118
+ constructor(e) {
119
+ this.accessToken = null, this.refreshToken = null, this.refreshTimer = null, this.refreshInFlight = null, this.onRefreshNeeded = null, this.onTokensChanged = null, this.boundHandleStorage = null, this.storageKey = `ss_rt_${e.slice(0, 12)}`, this.refreshToken = this.loadRefreshToken(), typeof window < "u" && (this.boundHandleStorage = this.handleStorageEvent.bind(this), window.addEventListener("storage", this.boundHandleStorage));
70
120
  }
71
- setRefreshCallback(t) {
72
- this.onRefreshNeeded = t;
121
+ setRefreshCallback(e) {
122
+ this.onRefreshNeeded = e;
123
+ }
124
+ setTokensChangedCallback(e) {
125
+ this.onTokensChanged = e;
73
126
  }
74
127
  getAccessToken() {
75
128
  return this.accessToken;
@@ -80,34 +133,64 @@ class w {
80
133
  hasRefreshToken() {
81
134
  return this.refreshToken !== null;
82
135
  }
83
- setTokens(t, e) {
84
- this.accessToken = t, this.refreshToken = e, this.saveRefreshToken(e), this.scheduleRefresh(t);
136
+ setTokens(e, t) {
137
+ this.accessToken = e, this.refreshToken = t, this.saveRefreshToken(t), this.scheduleRefresh(e);
85
138
  }
86
139
  clearTokens() {
87
140
  this.accessToken = null, this.refreshToken = null, this.removeRefreshToken(), this.refreshTimer && (clearTimeout(this.refreshTimer), this.refreshTimer = null);
88
141
  }
142
+ /**
143
+ * Coalesces concurrent refresh calls within this tab and coordinates
144
+ * across tabs via Web Locks API (when available).
145
+ */
146
+ async refreshOnce() {
147
+ return this.refreshInFlight ? this.refreshInFlight : (this.refreshInFlight = this.executeRefresh().finally(() => {
148
+ this.refreshInFlight = null;
149
+ }), this.refreshInFlight);
150
+ }
89
151
  destroy() {
90
- this.refreshTimer && (clearTimeout(this.refreshTimer), this.refreshTimer = null);
152
+ this.refreshTimer && (clearTimeout(this.refreshTimer), this.refreshTimer = null), typeof window < "u" && this.boundHandleStorage && (window.removeEventListener("storage", this.boundHandleStorage), this.boundHandleStorage = null);
153
+ }
154
+ async executeRefresh() {
155
+ if (!this.onRefreshNeeded)
156
+ throw new Error("No refresh callback configured");
157
+ typeof navigator < "u" && "locks" in navigator ? await navigator.locks.request(
158
+ `ss_refresh_lock_${this.storageKey}`,
159
+ async () => {
160
+ const e = this.loadRefreshToken();
161
+ e && e !== this.refreshToken && (this.refreshToken = e), await this.onRefreshNeeded();
162
+ }
163
+ ) : await this.onRefreshNeeded();
91
164
  }
92
- scheduleRefresh(t) {
93
- var r;
165
+ scheduleRefresh(e) {
94
166
  this.refreshTimer && clearTimeout(this.refreshTimer);
95
- const e = this.getTokenExpiry(t);
96
- if (!e) return;
97
- const s = e * 1e3 - Date.now() - 6e4;
167
+ const t = this.getTokenExpiry(e);
168
+ if (!t) return;
169
+ const s = t * 1e3 - Date.now() - 6e4;
98
170
  if (s <= 0) {
99
- (r = this.onRefreshNeeded) == null || r.call(this);
171
+ this.refreshOnce().catch(() => {
172
+ });
100
173
  return;
101
174
  }
102
175
  this.refreshTimer = setTimeout(() => {
103
- var i;
104
- (i = this.onRefreshNeeded) == null || i.call(this);
176
+ this.refreshOnce().catch(() => {
177
+ });
105
178
  }, s);
106
179
  }
107
- getTokenExpiry(t) {
180
+ handleStorageEvent(e) {
181
+ var t;
182
+ if (e.key === this.storageKey) {
183
+ if (e.newValue === null) {
184
+ this.accessToken = null, this.refreshToken = null, this.refreshTimer && (clearTimeout(this.refreshTimer), this.refreshTimer = null), (t = this.onTokensChanged) == null || t.call(this);
185
+ return;
186
+ }
187
+ e.newValue !== this.refreshToken && (this.refreshToken = e.newValue, this.accessToken = null, this.refreshTimer && (clearTimeout(this.refreshTimer), this.refreshTimer = null));
188
+ }
189
+ }
190
+ getTokenExpiry(e) {
108
191
  try {
109
- const e = t.split(".")[1];
110
- return JSON.parse(atob(e)).exp ?? null;
192
+ const t = e.split(".")[1];
193
+ return JSON.parse(atob(t)).exp ?? null;
111
194
  } catch {
112
195
  return null;
113
196
  }
@@ -119,9 +202,9 @@ class w {
119
202
  return null;
120
203
  }
121
204
  }
122
- saveRefreshToken(t) {
205
+ saveRefreshToken(e) {
123
206
  try {
124
- localStorage.setItem(this.storageKey, t);
207
+ localStorage.setItem(this.storageKey, e);
125
208
  } catch {
126
209
  }
127
210
  }
@@ -136,41 +219,41 @@ class S {
136
219
  constructor() {
137
220
  this.listeners = /* @__PURE__ */ new Map();
138
221
  }
139
- on(t, e) {
140
- return this.listeners.has(t) || this.listeners.set(t, /* @__PURE__ */ new Set()), this.listeners.get(t).add(e), () => {
222
+ on(e, t) {
223
+ return this.listeners.has(e) || this.listeners.set(e, /* @__PURE__ */ new Set()), this.listeners.get(e).add(t), () => {
141
224
  var s;
142
- (s = this.listeners.get(t)) == null || s.delete(e);
225
+ (s = this.listeners.get(e)) == null || s.delete(t);
143
226
  };
144
227
  }
145
- emit(t, e) {
228
+ emit(e, t) {
146
229
  var s;
147
- (s = this.listeners.get(t)) == null || s.forEach((r) => r(e));
230
+ (s = this.listeners.get(e)) == null || s.forEach((r) => r(t));
148
231
  }
149
232
  removeAll() {
150
233
  this.listeners.clear();
151
234
  }
152
235
  }
153
- const k = 500, T = 600, $ = 5 * 60 * 1e3;
154
- class R {
155
- constructor(t, e, s, r) {
156
- this.cachedUser = null, this.cachedSettings = null, this.loaded = !1, this.transport = t, this.tokenManager = e, this.emitter = s, this.baseUrl = r;
236
+ const w = 500, T = 600, U = 5 * 60 * 1e3;
237
+ class $ {
238
+ constructor(e, t, s, r) {
239
+ this.cachedUser = null, this.cachedSettings = null, this.loaded = !1, this.transport = e, this.tokenManager = t, this.emitter = s, this.baseUrl = r;
157
240
  }
158
241
  // ---------------------------------------------------------------------------
159
242
  // Lifecycle
160
243
  // ---------------------------------------------------------------------------
161
244
  async load() {
162
- var t, e;
245
+ var e, t;
163
246
  if (!this.loaded) {
164
247
  try {
165
248
  this.cachedSettings = await this.transport.get("/auth/settings");
166
249
  } catch (s) {
167
250
  console.warn("[SaaS Support] Failed to load project settings:", s);
168
251
  }
169
- if ((t = this.tokenManager) != null && t.hasRefreshToken())
252
+ if ((e = this.tokenManager) != null && e.hasRefreshToken())
170
253
  try {
171
254
  await this.performRefresh();
172
255
  } catch {
173
- (e = this.tokenManager) == null || e.clearTokens();
256
+ (t = this.tokenManager) == null || t.clearTokens();
174
257
  }
175
258
  this.loaded = !0;
176
259
  }
@@ -178,82 +261,82 @@ class R {
178
261
  // ---------------------------------------------------------------------------
179
262
  // Core auth operations
180
263
  // ---------------------------------------------------------------------------
181
- async signIn(t, e) {
182
- const s = await this.transport.post("/auth/login", { email: t, password: e });
264
+ async signIn(e, t) {
265
+ const s = await this.transport.post("/auth/login", { email: e, password: t });
183
266
  if ("mfaRequired" in s && s.mfaRequired)
184
267
  return s;
185
268
  const r = s;
186
269
  return this.setSession(r), r;
187
270
  }
188
- async signUp(t, e) {
189
- const s = await this.transport.post("/auth/register", { email: t, password: e });
271
+ async signUp(e, t) {
272
+ const s = await this.transport.post("/auth/register", { email: e, password: t });
190
273
  return this.setSession(s), s;
191
274
  }
192
275
  async signOut() {
193
- var e;
194
- const t = (e = this.tokenManager) == null ? void 0 : e.getRefreshToken();
195
- if (t)
276
+ var t;
277
+ const e = (t = this.tokenManager) == null ? void 0 : t.getRefreshToken();
278
+ if (e)
196
279
  try {
197
- await this.transport.post("/auth/logout", { refreshToken: t });
280
+ await this.transport.post("/auth/logout", { refreshToken: e });
198
281
  } catch {
199
282
  }
200
283
  this.clearSession();
201
284
  }
202
- async signInWithOAuth(t) {
203
- const e = `${this.baseUrl}/auth/oauth/${t}/popup-callback`, { authUrl: s, state: r } = await this.transport.get(
204
- `/auth/oauth/${t}?redirect_uri=${encodeURIComponent(e)}`
205
- ), i = window.screenX + (window.innerWidth - k) / 2, g = window.screenY + (window.innerHeight - T) / 2, a = window.open(
285
+ async signInWithOAuth(e) {
286
+ const t = `${this.baseUrl}/auth/oauth/${e}/popup-callback`, { authUrl: s, state: r } = await this.transport.get(
287
+ `/auth/oauth/${e}?redirect_uri=${encodeURIComponent(t)}`
288
+ ), n = window.screenX + (window.innerWidth - w) / 2, i = window.screenY + (window.innerHeight - T) / 2, a = window.open(
206
289
  s,
207
290
  "saas-support-oauth",
208
- `width=${k},height=${T},left=${i},top=${g},toolbar=no,menubar=no`
291
+ `width=${w},height=${T},left=${n},top=${i},toolbar=no,menubar=no`
209
292
  );
210
- return new Promise((d, c) => {
211
- let o = !1;
212
- const u = async (h) => {
213
- var f;
214
- if (((f = h.data) == null ? void 0 : f.type) === "saas-support:oauth-callback" && !o) {
215
- if (o = !0, window.removeEventListener("message", u), clearTimeout(y), clearInterval(p), a == null || a.close(), h.data.error) {
216
- c(new Error(`OAuth error: ${h.data.error}`));
293
+ return new Promise((p, c) => {
294
+ let h = !1;
295
+ const l = async (u) => {
296
+ var m;
297
+ if (((m = u.data) == null ? void 0 : m.type) === "saas-support:oauth-callback" && !h) {
298
+ if (h = !0, window.removeEventListener("message", l), clearTimeout(y), clearInterval(f), a == null || a.close(), u.data.error) {
299
+ c(new Error(`OAuth error: ${u.data.error}`));
217
300
  return;
218
301
  }
219
302
  try {
220
- const l = await this.transport.post(
221
- `/auth/oauth/${t}/callback`,
222
- { code: h.data.code, state: h.data.state || r }
303
+ const d = await this.transport.post(
304
+ `/auth/oauth/${e}/callback`,
305
+ { code: u.data.code, state: u.data.state || r }
223
306
  );
224
- this.setSession(l), d(l);
225
- } catch (l) {
226
- c(l);
307
+ this.setSession(d), p(d);
308
+ } catch (d) {
309
+ c(d);
227
310
  }
228
311
  }
229
312
  };
230
- window.addEventListener("message", u);
313
+ window.addEventListener("message", l);
231
314
  const y = setTimeout(() => {
232
- o || (o = !0, window.removeEventListener("message", u), clearInterval(p), a == null || a.close(), c(new Error("OAuth popup timed out")));
233
- }, $), p = setInterval(() => {
234
- a != null && a.closed && !o && (o = !0, clearInterval(p), clearTimeout(y), window.removeEventListener("message", u), c(new Error("OAuth popup was closed")));
315
+ h || (h = !0, window.removeEventListener("message", l), clearInterval(f), a == null || a.close(), c(new Error("OAuth popup timed out")));
316
+ }, U), f = setInterval(() => {
317
+ a != null && a.closed && !h && (h = !0, clearInterval(f), clearTimeout(y), window.removeEventListener("message", l), c(new Error("OAuth popup was closed")));
235
318
  }, 500);
236
319
  });
237
320
  }
238
- async submitMfaCode(t, e) {
239
- const s = await this.transport.post("/auth/login/mfa", { mfaToken: t, code: e });
321
+ async submitMfaCode(e, t) {
322
+ const s = await this.transport.post("/auth/login/mfa", { mfaToken: e, code: t });
240
323
  return this.setSession(s), s;
241
324
  }
242
325
  // ---------------------------------------------------------------------------
243
326
  // Magic link & password reset
244
327
  // ---------------------------------------------------------------------------
245
- async sendMagicLink(t, e) {
246
- await this.transport.post("/auth/magic-link/send", { email: t, redirectUrl: e });
328
+ async sendMagicLink(e, t) {
329
+ await this.transport.post("/auth/magic-link/send", { email: e, redirectUrl: t });
247
330
  }
248
- async verifyMagicLink(t) {
249
- const e = await this.transport.post("/auth/magic-link/verify", { token: t });
250
- return this.setSession(e), e;
331
+ async verifyMagicLink(e) {
332
+ const t = await this.transport.post("/auth/magic-link/verify", { token: e });
333
+ return this.setSession(t), t;
251
334
  }
252
- async sendPasswordReset(t, e) {
253
- await this.transport.post("/auth/password-reset/send", { email: t, redirectUrl: e });
335
+ async sendPasswordReset(e, t) {
336
+ await this.transport.post("/auth/password-reset/send", { email: e, redirectUrl: t });
254
337
  }
255
- async resetPassword(t, e) {
256
- await this.transport.post("/auth/password-reset/verify", { token: t, newPassword: e });
338
+ async resetPassword(e, t) {
339
+ await this.transport.post("/auth/password-reset/verify", { token: e, newPassword: t });
257
340
  }
258
341
  // ---------------------------------------------------------------------------
259
342
  // MFA management
@@ -261,22 +344,22 @@ class R {
261
344
  async setupMfa() {
262
345
  return this.transport.post("/auth/mfa/setup", void 0, this.authHeaders());
263
346
  }
264
- async verifyMfa(t) {
265
- return this.transport.post("/auth/mfa/verify", { code: t }, this.authHeaders());
347
+ async verifyMfa(e) {
348
+ return this.transport.post("/auth/mfa/verify", { code: e }, this.authHeaders());
266
349
  }
267
- async disableMfa(t) {
268
- await this.transport.post("/auth/mfa/disable", { code: t }, this.authHeaders());
350
+ async disableMfa(e) {
351
+ await this.transport.post("/auth/mfa/disable", { code: e }, this.authHeaders());
269
352
  }
270
353
  // ---------------------------------------------------------------------------
271
354
  // Token & user access
272
355
  // ---------------------------------------------------------------------------
273
356
  async getToken() {
274
- var e, s, r;
275
- const t = ((e = this.tokenManager) == null ? void 0 : e.getAccessToken()) ?? null;
276
- if (t) return t;
357
+ var t, s, r;
358
+ const e = ((t = this.tokenManager) == null ? void 0 : t.getAccessToken()) ?? null;
359
+ if (e) return e;
277
360
  if ((s = this.tokenManager) != null && s.hasRefreshToken())
278
361
  try {
279
- return await this.performRefresh(), ((r = this.tokenManager) == null ? void 0 : r.getAccessToken()) ?? null;
362
+ return await this.tokenManager.refreshOnce(), ((r = this.tokenManager) == null ? void 0 : r.getAccessToken()) ?? null;
280
363
  } catch {
281
364
  return this.clearSession(), null;
282
365
  }
@@ -284,10 +367,10 @@ class R {
284
367
  }
285
368
  async getUser() {
286
369
  if (this.cachedUser) return this.cachedUser;
287
- const t = await this.getToken();
288
- if (!t) return null;
370
+ const e = await this.getToken();
371
+ if (!e) return null;
289
372
  try {
290
- return this.cachedUser = await this.transport.get("/auth/me", { Authorization: `Bearer ${t}` }), this.cachedUser;
373
+ return this.cachedUser = await this.transport.get("/auth/me", { Authorization: `Bearer ${e}` }), this.cachedUser;
291
374
  } catch {
292
375
  return null;
293
376
  }
@@ -306,18 +389,22 @@ class R {
306
389
  return null;
307
390
  }
308
391
  }
309
- onAuthStateChange(t) {
310
- return this.emitter.on("authStateChange", t);
392
+ onAuthStateChange(e) {
393
+ return this.emitter.on("authStateChange", e);
311
394
  }
312
395
  // ---------------------------------------------------------------------------
313
396
  // Profile
314
397
  // ---------------------------------------------------------------------------
315
- async updateProfile(t) {
316
- const e = await this.transport.patch("/auth/me", t, this.authHeaders());
317
- return this.cachedUser = e, this.emitter.emit("authStateChange", e), e;
398
+ async updateProfile(e) {
399
+ const t = await this.transport.patch("/auth/me", e, this.authHeaders());
400
+ return this.cachedUser = t, this.emitter.emit("authStateChange", t), t;
401
+ }
402
+ async uploadAvatar(e) {
403
+ const t = await this.transport.uploadBinary("/auth/avatar", e, this.authHeaders());
404
+ return this.cachedUser && (this.cachedUser = { ...this.cachedUser, avatarUrl: t.avatarUrl }, this.emitter.emit("authStateChange", this.cachedUser)), t;
318
405
  }
319
- async changePassword(t, e) {
320
- await this.transport.post("/auth/change-password", { currentPassword: t, newPassword: e }, this.authHeaders());
406
+ async changePassword(e, t) {
407
+ await this.transport.post("/auth/change-password", { currentPassword: e, newPassword: t }, this.authHeaders());
321
408
  }
322
409
  // ---------------------------------------------------------------------------
323
410
  // Organizations
@@ -325,180 +412,192 @@ class R {
325
412
  async listOrgs() {
326
413
  return this.transport.get("/auth/orgs", this.authHeaders());
327
414
  }
328
- async createOrg(t, e) {
329
- return this.transport.post("/auth/orgs", { name: t, slug: e }, this.authHeaders());
415
+ async createOrg(e, t) {
416
+ return this.transport.post("/auth/orgs", { name: e, slug: t }, this.authHeaders());
330
417
  }
331
- async getOrg(t) {
332
- return this.transport.get(`/auth/orgs/${t}`, this.authHeaders());
418
+ async getOrg(e) {
419
+ return this.transport.get(`/auth/orgs/${e}`, this.authHeaders());
333
420
  }
334
- async updateOrg(t, e) {
335
- return this.transport.patch(`/auth/orgs/${t}`, e, this.authHeaders());
421
+ async updateOrg(e, t) {
422
+ return this.transport.patch(`/auth/orgs/${e}`, t, this.authHeaders());
336
423
  }
337
- async deleteOrg(t) {
338
- await this.transport.del(`/auth/orgs/${t}`, this.authHeaders());
424
+ async deleteOrg(e) {
425
+ await this.transport.del(`/auth/orgs/${e}`, this.authHeaders());
339
426
  }
340
427
  // ---------------------------------------------------------------------------
341
428
  // Members & Invites
342
429
  // ---------------------------------------------------------------------------
343
- async listMembers(t) {
344
- return this.transport.get(`/auth/orgs/${t}/members`, this.authHeaders());
430
+ async listMembers(e) {
431
+ return this.transport.get(`/auth/orgs/${e}/members`, this.authHeaders());
345
432
  }
346
- async sendInvite(t, e, s) {
347
- return this.transport.post(`/auth/orgs/${t}/invites`, { email: e, role: s }, this.authHeaders());
433
+ async sendInvite(e, t, s) {
434
+ return this.transport.post(`/auth/orgs/${e}/invites`, { email: t, role: s }, this.authHeaders());
348
435
  }
349
- async updateMemberRole(t, e, s) {
350
- await this.transport.patch(`/auth/orgs/${t}/members/${e}`, { role: s }, this.authHeaders());
436
+ async updateMemberRole(e, t, s) {
437
+ await this.transport.patch(`/auth/orgs/${e}/members/${t}`, { role: s }, this.authHeaders());
351
438
  }
352
- async removeMember(t, e) {
353
- await this.transport.del(`/auth/orgs/${t}/members/${e}`, this.authHeaders());
439
+ async removeMember(e, t) {
440
+ await this.transport.del(`/auth/orgs/${e}/members/${t}`, this.authHeaders());
354
441
  }
355
- async acceptInvite(t) {
356
- return this.transport.post(`/auth/invites/${t}/accept`, void 0, this.authHeaders());
442
+ async acceptInvite(e) {
443
+ return this.transport.post(`/auth/invites/${e}/accept`, void 0, this.authHeaders());
357
444
  }
358
445
  // ---------------------------------------------------------------------------
359
446
  // Internal
360
447
  // ---------------------------------------------------------------------------
448
+ /** @internal Called when another tab logs out (via storage event). */
449
+ handleExternalLogout() {
450
+ this.cachedUser = null, this.emitter.emit("authStateChange", null);
451
+ }
361
452
  /** @internal */
362
453
  async performRefresh() {
363
454
  var s;
364
- const t = (s = this.tokenManager) == null ? void 0 : s.getRefreshToken();
365
- if (!t) throw new Error("No refresh token");
366
- const e = await this.transport.post(
455
+ const e = (s = this.tokenManager) == null ? void 0 : s.getRefreshToken();
456
+ if (!e) throw new Error("No refresh token");
457
+ const t = await this.transport.post(
367
458
  "/auth/refresh",
368
- { refreshToken: t }
459
+ { refreshToken: e }
369
460
  );
370
- if (this.tokenManager.setTokens(e.accessToken, e.refreshToken), !this.cachedUser)
461
+ if (this.tokenManager.setTokens(t.accessToken, t.refreshToken), !this.cachedUser)
371
462
  try {
372
- this.cachedUser = await this.transport.get("/auth/me", { Authorization: `Bearer ${e.accessToken}` }), this.emitter.emit("authStateChange", this.cachedUser);
463
+ this.cachedUser = await this.transport.get("/auth/me", { Authorization: `Bearer ${t.accessToken}` }), this.emitter.emit("authStateChange", this.cachedUser);
373
464
  } catch {
374
465
  }
375
466
  }
376
- setSession(t) {
377
- var e;
378
- (e = this.tokenManager) == null || e.setTokens(t.accessToken, t.refreshToken), this.cachedUser = t.user, this.emitter.emit("authStateChange", t.user);
467
+ setSession(e) {
468
+ var t;
469
+ (t = this.tokenManager) == null || t.setTokens(e.accessToken, e.refreshToken), this.cachedUser = e.user, this.emitter.emit("authStateChange", e.user);
379
470
  }
380
471
  clearSession() {
381
- var t;
382
- (t = this.tokenManager) == null || t.clearTokens(), this.cachedUser = null, this.emitter.emit("authStateChange", null);
472
+ var e;
473
+ (e = this.tokenManager) == null || e.clearTokens(), this.cachedUser = null, this.emitter.emit("authStateChange", null);
383
474
  }
384
475
  authHeaders() {
385
- var e;
386
- const t = (e = this.tokenManager) == null ? void 0 : e.getAccessToken();
387
- return t ? { Authorization: `Bearer ${t}` } : {};
476
+ var t;
477
+ const e = (t = this.tokenManager) == null ? void 0 : t.getAccessToken();
478
+ return e ? { Authorization: `Bearer ${e}` } : {};
388
479
  }
389
480
  }
390
- class U {
391
- constructor(t) {
392
- this.transport = t;
481
+ class R {
482
+ constructor(e) {
483
+ this.transport = e;
393
484
  }
394
485
  // --- Customer ---
395
- async createCustomer(t) {
396
- return this.transport.post("/billing/customers", t);
486
+ async createCustomer(e) {
487
+ return this.transport.post("/billing/customers", e);
397
488
  }
398
- async getCustomer(t) {
399
- return this.transport.get(`/billing/customers/${t}`);
489
+ async getCustomer(e) {
490
+ return this.transport.get(`/billing/customers/${e}`);
400
491
  }
401
- async updateCustomer(t, e) {
402
- return this.transport.patch(`/billing/customers/${t}`, e);
492
+ async updateCustomer(e, t) {
493
+ return this.transport.patch(`/billing/customers/${e}`, t);
403
494
  }
404
495
  // --- Subscription ---
405
- async subscribe(t, e) {
406
- return this.transport.post(`/billing/customers/${t}/subscribe`, { planId: e });
496
+ async subscribe(e, t) {
497
+ return this.transport.post(`/billing/customers/${e}/subscribe`, { planId: t });
407
498
  }
408
- async changePlan(t, e) {
409
- return this.transport.patch(`/billing/customers/${t}/subscription`, { planId: e });
499
+ async changePlan(e, t) {
500
+ return this.transport.patch(`/billing/customers/${e}/subscription`, { planId: t });
410
501
  }
411
- async cancelSubscription(t) {
412
- return this.transport.del(`/billing/customers/${t}/subscription`);
502
+ async cancelSubscription(e) {
503
+ return this.transport.del(`/billing/customers/${e}/subscription`);
413
504
  }
414
505
  // --- Invoices ---
415
- async getInvoices(t) {
416
- return this.transport.get(`/billing/customers/${t}/invoices`);
506
+ async getInvoices(e) {
507
+ return this.transport.get(`/billing/customers/${e}/invoices`);
417
508
  }
418
509
  // --- Usage ---
419
- async ingestUsageEvent(t) {
420
- return this.transport.post("/billing/events", t);
510
+ async ingestUsageEvent(e) {
511
+ return this.transport.post("/billing/events", e);
421
512
  }
422
- async getCurrentUsage(t) {
423
- return this.transport.get(`/billing/customers/${t}/usage`);
513
+ async getCurrentUsage(e) {
514
+ return this.transport.get(`/billing/customers/${e}/usage`);
424
515
  }
425
516
  // --- Portal ---
426
- async createPortalToken(t, e) {
427
- return this.transport.post("/billing/portal-tokens", { customerId: t, expiresIn: e });
517
+ async createPortalToken(e, t) {
518
+ return this.transport.post("/billing/portal-tokens", { customerId: e, expiresIn: t });
428
519
  }
429
520
  // --- Coupon ---
430
- async applyCoupon(t, e) {
431
- return this.transport.post(`/billing/customers/${t}/coupon`, { code: e });
521
+ async applyCoupon(e, t) {
522
+ return this.transport.post(`/billing/customers/${e}/coupon`, { code: t });
432
523
  }
433
524
  }
434
525
  class M {
435
- constructor(t) {
436
- this.transport = t;
526
+ constructor(e) {
527
+ this.transport = e;
437
528
  }
438
529
  // --- Query ---
439
- async executeQuery(t) {
440
- return this.transport.post("/report/query", t);
530
+ async executeQuery(e) {
531
+ return this.transport.post("/report/query", e);
441
532
  }
442
533
  // --- Saved Queries ---
443
- async listQueries(t) {
444
- const e = t ? this.toQueryString(t) : "";
445
- return this.transport.get(`/report/queries${e}`);
534
+ async listQueries(e) {
535
+ const t = e ? this.toQueryString(e) : "";
536
+ return this.transport.get(`/report/queries${t}`);
446
537
  }
447
- async saveQuery(t) {
448
- return this.transport.post("/report/queries", t);
538
+ async saveQuery(e) {
539
+ return this.transport.post("/report/queries", e);
449
540
  }
450
- async updateQuery(t, e) {
451
- return this.transport.patch(`/report/queries/${t}`, e);
541
+ async updateQuery(e, t) {
542
+ return this.transport.patch(`/report/queries/${e}`, t);
452
543
  }
453
- async deleteQuery(t) {
454
- await this.transport.del(`/report/queries/${t}`);
544
+ async deleteQuery(e) {
545
+ await this.transport.del(`/report/queries/${e}`);
455
546
  }
456
547
  // --- Dashboards ---
457
- async listDashboards(t) {
458
- const e = t ? this.toQueryString(t) : "";
459
- return this.transport.get(`/report/dashboards${e}`);
548
+ async listDashboards(e) {
549
+ const t = e ? this.toQueryString(e) : "";
550
+ return this.transport.get(`/report/dashboards${t}`);
460
551
  }
461
- async createDashboard(t) {
462
- return this.transport.post("/report/dashboards", t);
552
+ async createDashboard(e) {
553
+ return this.transport.post("/report/dashboards", e);
463
554
  }
464
- async getDashboard(t) {
465
- return this.transport.get(`/report/dashboards/${t}`);
555
+ async getDashboard(e) {
556
+ return this.transport.get(`/report/dashboards/${e}`);
466
557
  }
467
- async updateDashboard(t, e) {
468
- return this.transport.patch(`/report/dashboards/${t}`, e);
558
+ async updateDashboard(e, t) {
559
+ return this.transport.patch(`/report/dashboards/${e}`, t);
469
560
  }
470
- async deleteDashboard(t) {
471
- await this.transport.del(`/report/dashboards/${t}`);
561
+ async deleteDashboard(e) {
562
+ await this.transport.del(`/report/dashboards/${e}`);
472
563
  }
473
564
  // --- Embed Tokens ---
474
- async createEmbedToken(t) {
475
- return this.transport.post("/report/embed-tokens", t);
565
+ async createEmbedToken(e) {
566
+ return this.transport.post("/report/embed-tokens", e);
476
567
  }
477
568
  async listEmbedTokens() {
478
569
  return this.transport.get("/report/embed-tokens");
479
570
  }
480
- async revokeEmbedToken(t) {
481
- await this.transport.del(`/report/embed-tokens/${t}`);
571
+ async revokeEmbedToken(e) {
572
+ await this.transport.del(`/report/embed-tokens/${e}`);
482
573
  }
483
- toQueryString(t) {
484
- const e = Object.entries(t).filter(([, s]) => s != null && s !== "");
485
- return e.length === 0 ? "" : "?" + e.map(([s, r]) => `${s}=${encodeURIComponent(String(r))}`).join("&");
574
+ toQueryString(e) {
575
+ const t = Object.entries(e).filter(([, s]) => s != null && s !== "");
576
+ return t.length === 0 ? "" : "?" + t.map(([s, r]) => `${s}=${encodeURIComponent(String(r))}`).join("&");
486
577
  }
487
578
  }
488
579
  const v = "https://api.saas-support.com/v1";
489
- class E {
490
- constructor(t) {
491
- if (this.tokenManager = null, this.loaded = !1, !t.publishableKey && !t.apiKey)
580
+ class H {
581
+ constructor(e) {
582
+ if (this.tokenManager = null, this.loaded = !1, !e.publishableKey && !e.apiKey)
492
583
  throw new Error("SaaSSupport: either publishableKey or apiKey is required");
493
- const e = t.baseUrl ?? v;
584
+ const t = e.baseUrl ?? v;
494
585
  this.emitter = new S();
495
- const s = t.publishableKey ? new m(e, { type: "publishableKey", key: t.publishableKey }) : null, r = t.apiKey ? new m(e, { type: "apiKey", key: t.apiKey }) : null;
496
- t.publishableKey && (this.tokenManager = new w(t.publishableKey)), this.auth = new R(
586
+ const s = e.publishableKey ? new k(t, { type: "publishableKey", key: e.publishableKey }) : null, r = e.apiKey ? new k(t, { type: "apiKey", key: e.apiKey }) : null;
587
+ e.publishableKey && (this.tokenManager = new b(e.publishableKey)), this.auth = new $(
497
588
  s ?? r,
498
589
  this.tokenManager,
499
590
  this.emitter,
500
- e
501
- ), this.billing = new U(r ?? s), this.report = new M(r ?? s), this.tokenManager && this.tokenManager.setRefreshCallback(() => this.auth.performRefresh());
591
+ t
592
+ ), this.billing = new R(r ?? s), this.report = new M(r ?? s), this.tokenManager && (this.tokenManager.setRefreshCallback(() => this.auth.performRefresh()), this.tokenManager.setTokensChangedCallback(() => {
593
+ this.tokenManager.hasRefreshToken() || this.auth.handleExternalLogout();
594
+ })), this.tokenManager && s && s.setUnauthorizedHandler(async () => {
595
+ try {
596
+ return await this.tokenManager.refreshOnce(), this.tokenManager.getAccessToken();
597
+ } catch {
598
+ return null;
599
+ }
600
+ });
502
601
  }
503
602
  async load() {
504
603
  this.loaded || (await this.auth.load(), this.loaded = !0);
@@ -506,23 +605,23 @@ class E {
506
605
  isLoaded() {
507
606
  return this.loaded;
508
607
  }
509
- onError(t) {
510
- return this.emitter.on("error", t);
608
+ onError(e) {
609
+ return this.emitter.on("error", e);
511
610
  }
512
611
  destroy() {
513
- var t;
514
- (t = this.tokenManager) == null || t.destroy(), this.emitter.removeAll();
612
+ var e;
613
+ (e = this.tokenManager) == null || e.destroy(), this.emitter.removeAll();
515
614
  }
516
615
  }
517
- function H(n) {
518
- return "mfaRequired" in n && n.mfaRequired === !0;
616
+ function E(o) {
617
+ return "mfaRequired" in o && o.mfaRequired === !0;
519
618
  }
520
619
  export {
521
- R as AuthClient,
522
- U as BillingClient,
620
+ $ as AuthClient,
621
+ R as BillingClient,
523
622
  M as ReportClient,
524
- b as SaaSError,
525
- E as SaaSSupport,
526
- m as Transport,
527
- H as isMfaRequired
623
+ g as SaaSError,
624
+ H as SaaSSupport,
625
+ k as Transport,
626
+ E as isMfaRequired
528
627
  };