@strands.gg/accui 1.8.0 → 1.10.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/accui.css +1 -5802
- package/dist/nuxt/module.cjs.js +1 -109
- package/dist/nuxt/module.cjs.js.map +1 -1
- package/dist/nuxt/module.es.js +0 -1
- package/dist/nuxt/runtime/composables/useStrandsAuth.cjs.js +1 -57
- package/dist/nuxt/runtime/composables/useStrandsAuth.cjs.js.map +1 -1
- package/dist/nuxt/runtime/composables/useStrandsAuth.es.js +1 -2
- package/dist/nuxt/runtime/middleware/auth.global.cjs.js +1 -42
- package/dist/nuxt/runtime/middleware/auth.global.cjs.js.map +1 -1
- package/dist/nuxt/runtime/middleware/auth.global.es.js +0 -1
- package/dist/nuxt/runtime/plugin.client.cjs.js +1 -26
- package/dist/nuxt/runtime/plugin.client.cjs.js.map +1 -1
- package/dist/nuxt/runtime/plugin.client.es.js +2 -1
- package/dist/nuxt/runtime/plugin.client.es.js.map +1 -1
- package/dist/nuxt/runtime/plugin.server.cjs.js +1 -17
- package/dist/nuxt/runtime/plugin.server.cjs.js.map +1 -1
- package/dist/nuxt/runtime/plugin.server.es.js +0 -1
- package/dist/nuxt-v4/module.cjs.js +1 -119
- package/dist/nuxt-v4/module.cjs.js.map +1 -1
- package/dist/nuxt-v4/module.es.js +0 -1
- package/dist/nuxt-v4/runtime/composables/useStrandsAuth.cjs.js +1 -70
- package/dist/nuxt-v4/runtime/composables/useStrandsAuth.cjs.js.map +1 -1
- package/dist/nuxt-v4/runtime/composables/useStrandsAuth.es.js +1 -2
- package/dist/nuxt-v4/runtime/middleware/auth.global.cjs.js +1 -42
- package/dist/nuxt-v4/runtime/middleware/auth.global.cjs.js.map +1 -1
- package/dist/nuxt-v4/runtime/middleware/auth.global.es.js +0 -1
- package/dist/nuxt-v4/runtime/plugin.client.cjs.js +1 -26
- package/dist/nuxt-v4/runtime/plugin.client.cjs.js.map +1 -1
- package/dist/nuxt-v4/runtime/plugin.client.es.js +2 -1
- package/dist/nuxt-v4/runtime/plugin.client.es.js.map +1 -1
- package/dist/nuxt-v4/runtime/plugin.server.cjs.js +1 -22
- package/dist/nuxt-v4/runtime/plugin.server.cjs.js.map +1 -1
- package/dist/nuxt-v4/runtime/plugin.server.es.js +0 -1
- package/dist/strands-auth-ui.cjs.js +1 -9840
- package/dist/strands-auth-ui.cjs.js.map +1 -1
- package/dist/strands-auth-ui.es.js +1398 -808
- package/dist/strands-auth-ui.es.js.map +1 -1
- package/dist/useStrandsAuth-Co9ekmXB.cjs +1 -0
- package/dist/useStrandsAuth-Co9ekmXB.cjs.map +1 -0
- package/dist/{useStrandsAuth-Beee__9G.js → useStrandsAuth-kWUOoMm-.js} +314 -172
- package/dist/useStrandsAuth-kWUOoMm-.js.map +1 -0
- package/dist/useStrandsConfig-Cxb360Os.js +0 -1
- package/dist/useStrandsConfig-Z9_36OcV.cjs +1 -0
- package/dist/{useStrandsConfig-Dms13Zd0.cjs.map → useStrandsConfig-Z9_36OcV.cjs.map} +1 -1
- package/package.json +1 -1
- package/dist/useStrandsAuth-Beee__9G.js.map +0 -1
- package/dist/useStrandsAuth-JfbGH2c-.cjs +0 -755
- package/dist/useStrandsAuth-JfbGH2c-.cjs.map +0 -1
- package/dist/useStrandsConfig-Dms13Zd0.cjs +0 -179
|
@@ -1,38 +1,162 @@
|
|
|
1
|
-
import { ref, computed } from "vue";
|
|
1
|
+
import { ref, computed, onUnmounted } from "vue";
|
|
2
2
|
import { u as useStrandsConfig } from "./useStrandsConfig-Cxb360Os.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
3
|
+
class RequestCache {
|
|
4
|
+
cache = /* @__PURE__ */ new Map();
|
|
5
|
+
DEFAULT_TTL = 5 * 60 * 1e3;
|
|
6
|
+
// 5 minutes
|
|
7
|
+
/**
|
|
8
|
+
* Memoized fetch - prevents duplicate requests and caches results
|
|
9
|
+
*/
|
|
10
|
+
async fetch(key, fetcher, ttl = this.DEFAULT_TTL) {
|
|
11
|
+
const now = Date.now();
|
|
12
|
+
const entry = this.cache.get(key);
|
|
13
|
+
if (entry && now - entry.timestamp < entry.ttl) {
|
|
14
|
+
return entry.promise;
|
|
15
|
+
}
|
|
16
|
+
this.cleanExpired();
|
|
17
|
+
const promise = fetcher().finally(() => {
|
|
18
|
+
setTimeout(() => {
|
|
19
|
+
this.cache.delete(key);
|
|
20
|
+
}, ttl);
|
|
21
|
+
});
|
|
22
|
+
this.cache.set(key, {
|
|
23
|
+
promise,
|
|
24
|
+
timestamp: now,
|
|
25
|
+
ttl
|
|
26
|
+
});
|
|
27
|
+
return promise;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Clear all cached entries
|
|
31
|
+
*/
|
|
32
|
+
clear() {
|
|
33
|
+
this.cache.clear();
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Remove a specific cache entry
|
|
37
|
+
*/
|
|
38
|
+
invalidate(key) {
|
|
39
|
+
this.cache.delete(key);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Clean expired cache entries
|
|
43
|
+
*/
|
|
44
|
+
cleanExpired() {
|
|
45
|
+
const now = Date.now();
|
|
46
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
47
|
+
if (now - entry.timestamp > entry.ttl) {
|
|
48
|
+
this.cache.delete(key);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Get cache statistics (for debugging)
|
|
54
|
+
*/
|
|
55
|
+
getStats() {
|
|
56
|
+
return {
|
|
57
|
+
size: this.cache.size,
|
|
58
|
+
entries: Array.from(this.cache.keys())
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const requestCache = new RequestCache();
|
|
63
|
+
function useRequestCache() {
|
|
64
|
+
return {
|
|
65
|
+
fetch: requestCache.fetch.bind(requestCache),
|
|
66
|
+
clear: requestCache.clear.bind(requestCache),
|
|
67
|
+
invalidate: requestCache.invalidate.bind(requestCache),
|
|
68
|
+
getStats: requestCache.getStats.bind(requestCache)
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function debounce(func, wait) {
|
|
72
|
+
let timeout = null;
|
|
73
|
+
return (...args) => {
|
|
74
|
+
if (timeout) {
|
|
75
|
+
clearTimeout(timeout);
|
|
76
|
+
}
|
|
77
|
+
timeout = setTimeout(() => {
|
|
78
|
+
func(...args);
|
|
79
|
+
}, wait);
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const debouncedSetItem = debounce((key, value) => {
|
|
83
|
+
if (typeof window !== "undefined") {
|
|
84
|
+
localStorage.setItem(key, value);
|
|
85
|
+
}
|
|
86
|
+
}, 300);
|
|
87
|
+
const mapApiUserToFrontend = (apiUser) => {
|
|
88
|
+
return {
|
|
89
|
+
id: apiUser.id,
|
|
90
|
+
email: apiUser.email,
|
|
91
|
+
firstName: apiUser.first_name || apiUser.firstName || "",
|
|
92
|
+
lastName: apiUser.last_name || apiUser.lastName || "",
|
|
93
|
+
avatar: apiUser.avatar_url || apiUser.avatar,
|
|
94
|
+
mfaEnabled: apiUser.mfa_enabled ?? apiUser.mfaEnabled ?? false,
|
|
95
|
+
emailVerified: apiUser.email_verified ?? apiUser.emailVerified ?? false,
|
|
96
|
+
passwordUpdatedAt: apiUser.password_updated_at || apiUser.passwordUpdatedAt,
|
|
97
|
+
settings: apiUser.settings || {},
|
|
98
|
+
xp: apiUser.xp || 0,
|
|
99
|
+
level: apiUser.level || 1,
|
|
100
|
+
next_level_xp: apiUser.next_level_xp || apiUser.next_level_xp || 4,
|
|
101
|
+
username: apiUser.username,
|
|
102
|
+
usernameLastChangedAt: apiUser.username_last_changed_at || apiUser.usernameLastChangedAt,
|
|
103
|
+
createdAt: apiUser.created_at || apiUser.createdAt,
|
|
104
|
+
updatedAt: apiUser.updated_at || apiUser.updatedAt || (/* @__PURE__ */ new Date()).toISOString()
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
const globalState = {
|
|
108
|
+
currentUser: ref(null),
|
|
109
|
+
currentSession: ref(null),
|
|
110
|
+
loadingStates: ref({
|
|
111
|
+
initializing: true,
|
|
112
|
+
signingIn: false,
|
|
113
|
+
signingUp: false,
|
|
114
|
+
signingOut: false,
|
|
115
|
+
refreshingToken: false,
|
|
116
|
+
sendingMfaEmail: false,
|
|
117
|
+
verifyingMfa: false,
|
|
118
|
+
loadingProfile: false
|
|
119
|
+
}),
|
|
120
|
+
isInitialized: ref(false),
|
|
121
|
+
mfaRequired: ref(false),
|
|
122
|
+
mfaSessionId: ref(null),
|
|
123
|
+
availableMfaMethods: ref([])
|
|
124
|
+
};
|
|
30
125
|
let refreshTimer = null;
|
|
126
|
+
let refreshPromise = null;
|
|
31
127
|
function useStrandsAuth() {
|
|
128
|
+
const { getUrl } = useStrandsConfig();
|
|
129
|
+
const { fetch: cachedFetch, clear: clearCache, invalidate } = useRequestCache();
|
|
130
|
+
const { currentUser, currentSession, loadingStates, isInitialized, mfaRequired, mfaSessionId, availableMfaMethods } = globalState;
|
|
131
|
+
const isInitializing = computed(() => loadingStates.value.initializing);
|
|
132
|
+
const isSigningIn = computed(() => loadingStates.value.signingIn);
|
|
133
|
+
const isSigningUp = computed(() => loadingStates.value.signingUp);
|
|
134
|
+
const isSigningOut = computed(() => loadingStates.value.signingOut);
|
|
135
|
+
const isRefreshingToken = computed(() => loadingStates.value.refreshingToken);
|
|
136
|
+
const isSendingMfaEmail = computed(() => loadingStates.value.sendingMfaEmail);
|
|
137
|
+
const isVerifyingMfa = computed(() => loadingStates.value.verifyingMfa);
|
|
138
|
+
computed(() => loadingStates.value.loadingProfile);
|
|
139
|
+
const loading = computed(
|
|
140
|
+
() => loadingStates.value.signingIn || loadingStates.value.signingUp || loadingStates.value.signingOut || loadingStates.value.refreshingToken || loadingStates.value.sendingMfaEmail || loadingStates.value.verifyingMfa || loadingStates.value.loadingProfile
|
|
141
|
+
);
|
|
142
|
+
const isLoading = computed(() => loadingStates.value.initializing || loading.value);
|
|
143
|
+
const loadingMessage = computed(() => {
|
|
144
|
+
const states = loadingStates.value;
|
|
145
|
+
if (states.initializing) return "Checking authentication...";
|
|
146
|
+
if (states.signingIn) return "Signing you in...";
|
|
147
|
+
if (states.signingUp) return "Creating your account...";
|
|
148
|
+
if (states.signingOut) return "Signing you out...";
|
|
149
|
+
if (states.refreshingToken) return "Refreshing session...";
|
|
150
|
+
if (states.sendingMfaEmail) return "Sending verification code...";
|
|
151
|
+
if (states.verifyingMfa) return "Verifying code...";
|
|
152
|
+
if (states.loadingProfile) return "Loading profile...";
|
|
153
|
+
return "Loading...";
|
|
154
|
+
});
|
|
32
155
|
const getAuthHeaders = () => {
|
|
33
156
|
if (!currentSession.value?.accessToken) {
|
|
34
157
|
throw new Error("No access token available");
|
|
35
158
|
}
|
|
159
|
+
currentSession.value.expiresAt;
|
|
36
160
|
const headers = {
|
|
37
161
|
"Authorization": `Bearer ${currentSession.value.accessToken}`
|
|
38
162
|
};
|
|
@@ -99,16 +223,20 @@ function useStrandsAuth() {
|
|
|
99
223
|
};
|
|
100
224
|
const isAuthenticated = computed(() => currentUser.value !== null);
|
|
101
225
|
const signIn = async (credentials) => {
|
|
102
|
-
|
|
226
|
+
loadingStates.value.signingIn = true;
|
|
103
227
|
try {
|
|
104
228
|
mfaRequired.value = false;
|
|
105
229
|
mfaSessionId.value = null;
|
|
106
230
|
availableMfaMethods.value = [];
|
|
231
|
+
const headers = {
|
|
232
|
+
"Content-Type": "application/json"
|
|
233
|
+
};
|
|
234
|
+
if (typeof window !== "undefined" && window.location) {
|
|
235
|
+
headers["Origin"] = window.location.origin;
|
|
236
|
+
}
|
|
107
237
|
const response = await fetch(getUrl("signIn"), {
|
|
108
238
|
method: "POST",
|
|
109
|
-
headers
|
|
110
|
-
"Content-Type": "application/json"
|
|
111
|
-
},
|
|
239
|
+
headers,
|
|
112
240
|
body: JSON.stringify(credentials)
|
|
113
241
|
});
|
|
114
242
|
if (!response.ok) {
|
|
@@ -146,29 +274,31 @@ function useStrandsAuth() {
|
|
|
146
274
|
};
|
|
147
275
|
});
|
|
148
276
|
availableMfaMethods.value = methods;
|
|
149
|
-
|
|
277
|
+
loadingStates.value.signingIn = false;
|
|
150
278
|
return authData;
|
|
151
279
|
}
|
|
152
|
-
setAuthData(authData);
|
|
280
|
+
await setAuthData(authData);
|
|
153
281
|
return authData;
|
|
154
282
|
} catch (error) {
|
|
155
283
|
throw error;
|
|
156
284
|
} finally {
|
|
157
|
-
|
|
285
|
+
loadingStates.value.signingIn = false;
|
|
158
286
|
}
|
|
159
287
|
};
|
|
160
288
|
const signUp = async (userData) => {
|
|
161
|
-
|
|
289
|
+
loadingStates.value.signingUp = true;
|
|
162
290
|
try {
|
|
163
291
|
throw new Error("Sign up not implemented - please integrate with auth SDK");
|
|
164
292
|
} finally {
|
|
165
|
-
|
|
293
|
+
loadingStates.value.signingUp = false;
|
|
166
294
|
}
|
|
167
295
|
};
|
|
168
296
|
const signOut = async () => {
|
|
169
|
-
|
|
297
|
+
loadingStates.value.signingOut = true;
|
|
170
298
|
try {
|
|
171
299
|
stopTokenRefreshTimer();
|
|
300
|
+
refreshPromise = null;
|
|
301
|
+
clearCache();
|
|
172
302
|
currentUser.value = null;
|
|
173
303
|
currentSession.value = null;
|
|
174
304
|
mfaRequired.value = false;
|
|
@@ -179,102 +309,106 @@ function useStrandsAuth() {
|
|
|
179
309
|
localStorage.removeItem("strands_auth_user");
|
|
180
310
|
}
|
|
181
311
|
} finally {
|
|
182
|
-
|
|
312
|
+
loadingStates.value.signingOut = false;
|
|
183
313
|
}
|
|
184
314
|
};
|
|
185
315
|
const refreshToken = async () => {
|
|
186
316
|
if (!currentSession.value?.refreshToken) {
|
|
187
317
|
return false;
|
|
188
318
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
319
|
+
if (refreshPromise) {
|
|
320
|
+
console.log("Token refresh already in progress, waiting for completion...");
|
|
321
|
+
return await refreshPromise;
|
|
322
|
+
}
|
|
323
|
+
refreshPromise = (async () => {
|
|
324
|
+
loadingStates.value.refreshingToken = true;
|
|
325
|
+
try {
|
|
326
|
+
const response = await fetch(getUrl("refresh"), {
|
|
327
|
+
method: "POST",
|
|
328
|
+
headers: {
|
|
329
|
+
"Content-Type": "application/json"
|
|
330
|
+
},
|
|
331
|
+
body: JSON.stringify({
|
|
332
|
+
refresh_token: currentSession.value.refreshToken
|
|
333
|
+
})
|
|
334
|
+
});
|
|
335
|
+
if (!response.ok) {
|
|
336
|
+
if (response.status === 401) {
|
|
337
|
+
await signOut();
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
throw new Error(`Token refresh failed: ${response.status} ${response.statusText}`);
|
|
204
341
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
342
|
+
const tokenData = await response.json();
|
|
343
|
+
if (tokenData.user) {
|
|
344
|
+
currentUser.value = mapApiUserToFrontend(tokenData.user);
|
|
345
|
+
if (currentUser.value && typeof window !== "undefined") {
|
|
346
|
+
localStorage.setItem("strands_auth_user", JSON.stringify(currentUser.value));
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
const newSession = {
|
|
350
|
+
accessToken: tokenData.access_token,
|
|
351
|
+
refreshToken: tokenData.refresh_token,
|
|
352
|
+
expiresAt: new Date(Date.now() + 5 * 60 * 1e3),
|
|
353
|
+
// 5 minutes from now
|
|
354
|
+
userId: tokenData.user?.id || currentUser.value?.id
|
|
355
|
+
};
|
|
356
|
+
currentSession.value = newSession;
|
|
357
|
+
if (typeof window !== "undefined") {
|
|
358
|
+
localStorage.setItem("strands_auth_session", JSON.stringify(newSession));
|
|
359
|
+
}
|
|
360
|
+
startTokenRefreshTimer();
|
|
361
|
+
invalidate(`sessions:${currentSession.value.accessToken.slice(0, 20)}`);
|
|
362
|
+
return true;
|
|
363
|
+
} catch (error) {
|
|
364
|
+
await signOut();
|
|
365
|
+
return false;
|
|
366
|
+
} finally {
|
|
367
|
+
loadingStates.value.refreshingToken = false;
|
|
218
368
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
return false;
|
|
224
|
-
}
|
|
369
|
+
})();
|
|
370
|
+
const result = await refreshPromise;
|
|
371
|
+
refreshPromise = null;
|
|
372
|
+
return result;
|
|
225
373
|
};
|
|
226
374
|
const fetchProfile = async () => {
|
|
227
375
|
if (!currentSession.value?.accessToken) {
|
|
228
376
|
throw new Error("No access token available");
|
|
229
377
|
}
|
|
230
|
-
|
|
378
|
+
const cacheKey = `profile:${currentSession.value.accessToken.slice(0, 20)}`;
|
|
379
|
+
loadingStates.value.loadingProfile = true;
|
|
231
380
|
try {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
381
|
+
return await cachedFetch(cacheKey, async () => {
|
|
382
|
+
const response = await fetch(getUrl("profile"), {
|
|
383
|
+
method: "GET",
|
|
384
|
+
headers: {
|
|
385
|
+
"Content-Type": "application/json",
|
|
386
|
+
"Authorization": `Bearer ${currentSession.value?.accessToken}`
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
if (!response.ok) {
|
|
390
|
+
if (response.status === 401) {
|
|
391
|
+
throw new Error("Authentication expired. Please sign in again.");
|
|
392
|
+
} else {
|
|
393
|
+
throw new Error(`Failed to fetch profile: ${response.status} ${response.statusText}`);
|
|
394
|
+
}
|
|
237
395
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
if (
|
|
241
|
-
|
|
242
|
-
} else {
|
|
243
|
-
throw new Error(`Failed to fetch profile: ${response.status} ${response.statusText}`);
|
|
396
|
+
const userData = await response.json();
|
|
397
|
+
currentUser.value = mapApiUserToFrontend(userData);
|
|
398
|
+
if (currentUser.value && typeof window !== "undefined") {
|
|
399
|
+
localStorage.setItem("strands_auth_user", JSON.stringify(currentUser.value));
|
|
244
400
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
currentUser.value = {
|
|
248
|
-
id: userData.id,
|
|
249
|
-
email: userData.email,
|
|
250
|
-
firstName: userData.first_name,
|
|
251
|
-
lastName: userData.last_name,
|
|
252
|
-
avatar: userData.avatar_url,
|
|
253
|
-
mfaEnabled: userData.mfaEnabled || false,
|
|
254
|
-
emailVerified: userData.email_verified,
|
|
255
|
-
passwordUpdatedAt: userData.password_updated_at,
|
|
256
|
-
settings: userData.settings || {},
|
|
257
|
-
xp: userData.xp || 0,
|
|
258
|
-
level: userData.level || 1,
|
|
259
|
-
next_level_xp: userData.next_level_xp || 4,
|
|
260
|
-
username: userData.username,
|
|
261
|
-
usernameLastChangedAt: userData.username_last_changed_at,
|
|
262
|
-
createdAt: userData.created_at,
|
|
263
|
-
updatedAt: userData.updated_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
264
|
-
};
|
|
265
|
-
if (typeof window !== "undefined") {
|
|
266
|
-
localStorage.setItem("strands_auth_user", JSON.stringify(currentUser.value));
|
|
267
|
-
}
|
|
268
|
-
return currentUser.value;
|
|
401
|
+
return currentUser.value;
|
|
402
|
+
});
|
|
269
403
|
} finally {
|
|
270
|
-
|
|
404
|
+
loadingStates.value.loadingProfile = false;
|
|
271
405
|
}
|
|
272
406
|
};
|
|
273
407
|
const updateProfile = async (profileData) => {
|
|
274
408
|
if (!currentSession.value?.accessToken) {
|
|
275
409
|
throw new Error("No access token available");
|
|
276
410
|
}
|
|
277
|
-
|
|
411
|
+
loadingStates.value.loadingProfile = true;
|
|
278
412
|
try {
|
|
279
413
|
const response = await fetch(getUrl("profile"), {
|
|
280
414
|
method: "POST",
|
|
@@ -295,42 +429,20 @@ function useStrandsAuth() {
|
|
|
295
429
|
}
|
|
296
430
|
}
|
|
297
431
|
const updatedUserData = await response.json();
|
|
432
|
+
currentUser.value = mapApiUserToFrontend(updatedUserData);
|
|
298
433
|
if (currentUser.value) {
|
|
299
|
-
currentUser.value
|
|
300
|
-
...currentUser.value,
|
|
301
|
-
// Update with API response data, preserving the API field names
|
|
302
|
-
id: updatedUserData.id,
|
|
303
|
-
email: updatedUserData.email,
|
|
304
|
-
firstName: updatedUserData.first_name,
|
|
305
|
-
lastName: updatedUserData.last_name,
|
|
306
|
-
emailVerified: updatedUserData.email_verified,
|
|
307
|
-
avatar: updatedUserData.avatar_url,
|
|
308
|
-
mfaEnabled: updatedUserData.mfa_enabled,
|
|
309
|
-
xp: updatedUserData.xp,
|
|
310
|
-
level: updatedUserData.level,
|
|
311
|
-
next_level_xp: updatedUserData.next_level_xp,
|
|
312
|
-
username: updatedUserData.username,
|
|
313
|
-
// This was missing and causing the bug!
|
|
314
|
-
usernameLastChangedAt: updatedUserData.username_last_changed_at,
|
|
315
|
-
createdAt: updatedUserData.created_at,
|
|
316
|
-
updatedAt: updatedUserData.updated_at,
|
|
317
|
-
settings: updatedUserData.settings,
|
|
318
|
-
passwordUpdatedAt: updatedUserData.password_updated_at
|
|
319
|
-
};
|
|
320
|
-
if (typeof window !== "undefined") {
|
|
321
|
-
localStorage.setItem("strands_auth_user", JSON.stringify(currentUser.value));
|
|
322
|
-
}
|
|
434
|
+
debouncedSetItem("strands_auth_user", JSON.stringify(currentUser.value));
|
|
323
435
|
}
|
|
324
436
|
return currentUser.value;
|
|
325
437
|
} finally {
|
|
326
|
-
|
|
438
|
+
loadingStates.value.loadingProfile = false;
|
|
327
439
|
}
|
|
328
440
|
};
|
|
329
441
|
const updateUserSettings = async (settings) => {
|
|
330
442
|
if (!currentSession.value?.accessToken) {
|
|
331
443
|
throw new Error("No access token available");
|
|
332
444
|
}
|
|
333
|
-
|
|
445
|
+
loadingStates.value.loadingProfile = true;
|
|
334
446
|
try {
|
|
335
447
|
const response = await fetch(getUrl("settings"), {
|
|
336
448
|
method: "POST",
|
|
@@ -350,26 +462,20 @@ function useStrandsAuth() {
|
|
|
350
462
|
}
|
|
351
463
|
}
|
|
352
464
|
const updatedUserData = await response.json();
|
|
465
|
+
currentUser.value = mapApiUserToFrontend(updatedUserData);
|
|
353
466
|
if (currentUser.value) {
|
|
354
|
-
currentUser.value
|
|
355
|
-
...currentUser.value,
|
|
356
|
-
settings: updatedUserData.settings,
|
|
357
|
-
updatedAt: updatedUserData.updated_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
358
|
-
};
|
|
359
|
-
if (typeof window !== "undefined") {
|
|
360
|
-
localStorage.setItem("strands_auth_user", JSON.stringify(currentUser.value));
|
|
361
|
-
}
|
|
467
|
+
debouncedSetItem("strands_auth_user", JSON.stringify(currentUser.value));
|
|
362
468
|
}
|
|
363
469
|
return currentUser.value;
|
|
364
470
|
} finally {
|
|
365
|
-
|
|
471
|
+
loadingStates.value.loadingProfile = false;
|
|
366
472
|
}
|
|
367
473
|
};
|
|
368
474
|
const changeEmail = async (newEmail, password) => {
|
|
369
475
|
if (!currentSession.value?.accessToken) {
|
|
370
476
|
throw new Error("No access token available");
|
|
371
477
|
}
|
|
372
|
-
|
|
478
|
+
loadingStates.value.loadingProfile = true;
|
|
373
479
|
try {
|
|
374
480
|
const response = await fetch(getUrl("changeEmail"), {
|
|
375
481
|
method: "POST",
|
|
@@ -405,14 +511,14 @@ function useStrandsAuth() {
|
|
|
405
511
|
}
|
|
406
512
|
return result;
|
|
407
513
|
} finally {
|
|
408
|
-
|
|
514
|
+
loadingStates.value.loadingProfile = false;
|
|
409
515
|
}
|
|
410
516
|
};
|
|
411
517
|
const verifyMfa = async (deviceId, code, isBackupCode = false) => {
|
|
412
518
|
if (!mfaSessionId.value) {
|
|
413
519
|
throw new Error("No MFA session available");
|
|
414
520
|
}
|
|
415
|
-
|
|
521
|
+
loadingStates.value.verifyingMfa = true;
|
|
416
522
|
try {
|
|
417
523
|
const endpoint = isBackupCode ? getUrl("mfaBackupCodeVerify") : getUrl("mfaSigninVerify");
|
|
418
524
|
const body = isBackupCode ? { mfa_session_id: mfaSessionId.value, backup_code: code } : { mfa_session_id: mfaSessionId.value, device_id: deviceId, code };
|
|
@@ -436,17 +542,17 @@ function useStrandsAuth() {
|
|
|
436
542
|
mfaRequired.value = false;
|
|
437
543
|
mfaSessionId.value = null;
|
|
438
544
|
availableMfaMethods.value = [];
|
|
439
|
-
setAuthData(authData);
|
|
545
|
+
await setAuthData(authData);
|
|
440
546
|
return authData;
|
|
441
547
|
} finally {
|
|
442
|
-
|
|
548
|
+
loadingStates.value.verifyingMfa = false;
|
|
443
549
|
}
|
|
444
550
|
};
|
|
445
551
|
const sendMfaEmailCode = async (deviceId) => {
|
|
446
552
|
if (!mfaSessionId.value) {
|
|
447
553
|
throw new Error("No MFA session available");
|
|
448
554
|
}
|
|
449
|
-
|
|
555
|
+
loadingStates.value.sendingMfaEmail = true;
|
|
450
556
|
try {
|
|
451
557
|
const response = await fetch(getUrl("mfaSigninSendEmail"), {
|
|
452
558
|
method: "POST",
|
|
@@ -470,7 +576,7 @@ function useStrandsAuth() {
|
|
|
470
576
|
const result = await response.json();
|
|
471
577
|
return result;
|
|
472
578
|
} finally {
|
|
473
|
-
|
|
579
|
+
loadingStates.value.sendingMfaEmail = false;
|
|
474
580
|
}
|
|
475
581
|
};
|
|
476
582
|
const getMfaWebAuthnChallenge = async (deviceId) => {
|
|
@@ -498,10 +604,10 @@ function useStrandsAuth() {
|
|
|
498
604
|
}
|
|
499
605
|
return response.json();
|
|
500
606
|
};
|
|
501
|
-
const setAuthData = (authResponse) => {
|
|
607
|
+
const setAuthData = async (authResponse) => {
|
|
502
608
|
try {
|
|
503
609
|
if (authResponse.user) {
|
|
504
|
-
currentUser.value = authResponse.user;
|
|
610
|
+
currentUser.value = mapApiUserToFrontend(authResponse.user);
|
|
505
611
|
}
|
|
506
612
|
const session = {
|
|
507
613
|
accessToken: authResponse.access_token,
|
|
@@ -519,7 +625,6 @@ function useStrandsAuth() {
|
|
|
519
625
|
}
|
|
520
626
|
startTokenRefreshTimer();
|
|
521
627
|
} catch (error) {
|
|
522
|
-
console.error("Error setting auth data:", error);
|
|
523
628
|
}
|
|
524
629
|
};
|
|
525
630
|
const startTokenRefreshTimer = () => {
|
|
@@ -527,6 +632,9 @@ function useStrandsAuth() {
|
|
|
527
632
|
clearTimeout(refreshTimer);
|
|
528
633
|
}
|
|
529
634
|
if (!currentSession.value) return;
|
|
635
|
+
if (typeof document !== "undefined" && document.visibilityState === "hidden") {
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
530
638
|
const now = /* @__PURE__ */ new Date();
|
|
531
639
|
const expiresAt = currentSession.value.expiresAt;
|
|
532
640
|
const timeUntilRefresh = expiresAt.getTime() - now.getTime() - 1 * 60 * 1e3;
|
|
@@ -535,9 +643,11 @@ function useStrandsAuth() {
|
|
|
535
643
|
return;
|
|
536
644
|
}
|
|
537
645
|
refreshTimer = setTimeout(async () => {
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
646
|
+
if (typeof document === "undefined" || document.visibilityState === "visible") {
|
|
647
|
+
const success = await refreshToken();
|
|
648
|
+
if (success) {
|
|
649
|
+
startTokenRefreshTimer();
|
|
650
|
+
}
|
|
541
651
|
}
|
|
542
652
|
}, timeUntilRefresh);
|
|
543
653
|
};
|
|
@@ -549,7 +659,7 @@ function useStrandsAuth() {
|
|
|
549
659
|
};
|
|
550
660
|
const initialize = async () => {
|
|
551
661
|
if (isInitialized.value) return;
|
|
552
|
-
|
|
662
|
+
loadingStates.value.initializing = true;
|
|
553
663
|
try {
|
|
554
664
|
if (typeof window !== "undefined") {
|
|
555
665
|
const storedSession = localStorage.getItem("strands_auth_session");
|
|
@@ -577,14 +687,14 @@ function useStrandsAuth() {
|
|
|
577
687
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
578
688
|
} catch (error) {
|
|
579
689
|
} finally {
|
|
580
|
-
|
|
690
|
+
loadingStates.value.initializing = false;
|
|
581
691
|
}
|
|
582
692
|
};
|
|
583
693
|
const changeUsername = async (newUsername) => {
|
|
584
694
|
if (!currentSession.value?.accessToken) {
|
|
585
695
|
throw new Error("No access token available");
|
|
586
696
|
}
|
|
587
|
-
|
|
697
|
+
loadingStates.value.loadingProfile = true;
|
|
588
698
|
try {
|
|
589
699
|
const response = await fetch(getUrl("changeUsername"), {
|
|
590
700
|
method: "POST",
|
|
@@ -619,7 +729,7 @@ function useStrandsAuth() {
|
|
|
619
729
|
}
|
|
620
730
|
return result;
|
|
621
731
|
} finally {
|
|
622
|
-
|
|
732
|
+
loadingStates.value.loadingProfile = false;
|
|
623
733
|
}
|
|
624
734
|
};
|
|
625
735
|
const getUsernameCooldown = async () => {
|
|
@@ -651,14 +761,26 @@ function useStrandsAuth() {
|
|
|
651
761
|
return response.json();
|
|
652
762
|
};
|
|
653
763
|
const getUserSessions = async () => {
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
|
|
764
|
+
if (!currentSession.value?.accessToken) {
|
|
765
|
+
throw new Error("No access token available");
|
|
766
|
+
}
|
|
767
|
+
const cacheKey = `sessions:${currentSession.value.accessToken.slice(0, 20)}`;
|
|
768
|
+
try {
|
|
769
|
+
return await cachedFetch(cacheKey, async () => {
|
|
770
|
+
const headers = getAuthHeaders();
|
|
771
|
+
const response = await fetch(getUrl("sessions"), {
|
|
772
|
+
method: "GET",
|
|
773
|
+
headers
|
|
774
|
+
});
|
|
775
|
+
if (!response.ok) {
|
|
776
|
+
const errorText = await response.text();
|
|
777
|
+
throw new Error(`Failed to get user sessions: ${response.status} ${response.statusText}`);
|
|
778
|
+
}
|
|
779
|
+
return response.json();
|
|
780
|
+
}, 2 * 60 * 1e3);
|
|
781
|
+
} catch (error) {
|
|
782
|
+
throw error;
|
|
660
783
|
}
|
|
661
|
-
return response.json();
|
|
662
784
|
};
|
|
663
785
|
const getSessionStats = async () => {
|
|
664
786
|
const response = await fetch(getUrl("sessionsStats"), {
|
|
@@ -691,6 +813,27 @@ function useStrandsAuth() {
|
|
|
691
813
|
}
|
|
692
814
|
return response.status === 200;
|
|
693
815
|
};
|
|
816
|
+
if (typeof document !== "undefined") {
|
|
817
|
+
document.addEventListener("visibilitychange", () => {
|
|
818
|
+
if (document.visibilityState === "visible" && currentSession.value) {
|
|
819
|
+
startTokenRefreshTimer();
|
|
820
|
+
} else if (document.visibilityState === "hidden") {
|
|
821
|
+
stopTokenRefreshTimer();
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
const cleanup = () => {
|
|
826
|
+
stopTokenRefreshTimer();
|
|
827
|
+
clearCache();
|
|
828
|
+
if (typeof document !== "undefined") {
|
|
829
|
+
document.removeEventListener("visibilitychange", () => {
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
};
|
|
833
|
+
try {
|
|
834
|
+
onUnmounted(cleanup);
|
|
835
|
+
} catch (e) {
|
|
836
|
+
}
|
|
694
837
|
if (!isInitialized.value) {
|
|
695
838
|
initialize();
|
|
696
839
|
}
|
|
@@ -745,7 +888,7 @@ function useStrandsAuth() {
|
|
|
745
888
|
// Force re-initialization (useful for testing or navigation)
|
|
746
889
|
forceReInit: () => {
|
|
747
890
|
isInitialized.value = false;
|
|
748
|
-
|
|
891
|
+
loadingStates.value.initializing = true;
|
|
749
892
|
initialize();
|
|
750
893
|
}
|
|
751
894
|
};
|
|
@@ -753,4 +896,3 @@ function useStrandsAuth() {
|
|
|
753
896
|
export {
|
|
754
897
|
useStrandsAuth as u
|
|
755
898
|
};
|
|
756
|
-
//# sourceMappingURL=useStrandsAuth-Beee__9G.js.map
|