@sanvika/auth 1.0.10 → 1.0.11
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 +117 -6
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,7 +6,8 @@ import {
|
|
|
6
6
|
useContext,
|
|
7
7
|
useEffect,
|
|
8
8
|
useState,
|
|
9
|
-
useCallback
|
|
9
|
+
useCallback,
|
|
10
|
+
useRef
|
|
10
11
|
} from "react";
|
|
11
12
|
|
|
12
13
|
// constants.js
|
|
@@ -21,6 +22,74 @@ var STORAGE_KEYS = {
|
|
|
21
22
|
var DEFAULT_IAM_URL = "https://accounts.sanvikaproduction.com";
|
|
22
23
|
var DEFAULT_AVATAR_SVG = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 40'%3E%3Ccircle cx='20' cy='20' r='20' fill='%23e5e7eb'/%3E%3Ccircle cx='20' cy='15' r='7' fill='%23adb5bd'/%3E%3Cellipse cx='20' cy='35' rx='12' ry='8' fill='%23adb5bd'/%3E%3C/svg%3E`;
|
|
23
24
|
|
|
25
|
+
// tokenRefreshUtils.js
|
|
26
|
+
function decodeToken(token) {
|
|
27
|
+
try {
|
|
28
|
+
const base64Url = token.split(".")[1];
|
|
29
|
+
const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
|
|
30
|
+
const jsonPayload = decodeURIComponent(
|
|
31
|
+
atob(base64).split("").map((c) => "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2)).join("")
|
|
32
|
+
);
|
|
33
|
+
return JSON.parse(jsonPayload);
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async function ensureTokenFresh(token, thresholdSeconds = 300) {
|
|
39
|
+
if (!token) return null;
|
|
40
|
+
const decoded = decodeToken(token);
|
|
41
|
+
if (!decoded || !decoded.exp) return token;
|
|
42
|
+
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
43
|
+
const secondsUntilExpiry = decoded.exp - nowSeconds;
|
|
44
|
+
if (secondsUntilExpiry > thresholdSeconds) {
|
|
45
|
+
return token;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const refreshRes = await fetch(
|
|
49
|
+
`${process.env.NEXT_PUBLIC_APP_BASE_URL || "https://accounts.sanvikaproduction.com"}/api/auth/refresh`,
|
|
50
|
+
{
|
|
51
|
+
method: "POST",
|
|
52
|
+
headers: { "Content-Type": "application/json" },
|
|
53
|
+
credentials: "include"
|
|
54
|
+
// Send refresh token cookie
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
if (!refreshRes.ok) {
|
|
58
|
+
console.warn("[TokenRefresh] Refresh failed:", refreshRes.status);
|
|
59
|
+
return token;
|
|
60
|
+
}
|
|
61
|
+
const data = await refreshRes.json();
|
|
62
|
+
if (data.success && data.accessToken) {
|
|
63
|
+
localStorage.setItem("sanvika_accessToken", data.accessToken);
|
|
64
|
+
return data.accessToken;
|
|
65
|
+
}
|
|
66
|
+
} catch (err) {
|
|
67
|
+
console.warn("[TokenRefresh] Error refreshing token:", err.message);
|
|
68
|
+
}
|
|
69
|
+
return token;
|
|
70
|
+
}
|
|
71
|
+
function scheduleTokenRefresh(token, onRefresh) {
|
|
72
|
+
if (!token) return () => {
|
|
73
|
+
};
|
|
74
|
+
const decoded = decodeToken(token);
|
|
75
|
+
if (!decoded || !decoded.exp) return () => {
|
|
76
|
+
};
|
|
77
|
+
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
78
|
+
const secondsUntilExpiry = decoded.exp - nowSeconds;
|
|
79
|
+
const refreshAt = Math.max(60, Math.floor(secondsUntilExpiry * 0.75));
|
|
80
|
+
const timeoutId = setTimeout(async () => {
|
|
81
|
+
try {
|
|
82
|
+
const freshToken = await ensureTokenFresh(token, 0);
|
|
83
|
+
if (freshToken !== token && onRefresh) {
|
|
84
|
+
onRefresh(freshToken);
|
|
85
|
+
}
|
|
86
|
+
} catch (err) {
|
|
87
|
+
console.warn("[TokenRefresh] Auto-refresh failed:", err.message);
|
|
88
|
+
}
|
|
89
|
+
}, refreshAt * 1e3);
|
|
90
|
+
return () => clearTimeout(timeoutId);
|
|
91
|
+
}
|
|
92
|
+
|
|
24
93
|
// SanvikaAuthProvider.jsx
|
|
25
94
|
import { jsx } from "react/jsx-runtime";
|
|
26
95
|
var SanvikaAuthContext = createContext(null);
|
|
@@ -34,6 +103,7 @@ function SanvikaAuthProvider({
|
|
|
34
103
|
const [user, setUser] = useState(null);
|
|
35
104
|
const [accessToken, setToken] = useState(null);
|
|
36
105
|
const [loading, setLoading] = useState(true);
|
|
106
|
+
const refreshCleanupRef = useRef(null);
|
|
37
107
|
useEffect(() => {
|
|
38
108
|
try {
|
|
39
109
|
const storedToken = localStorage.getItem(STORAGE_KEYS.ACCESS_TOKEN);
|
|
@@ -67,6 +137,30 @@ function SanvikaAuthProvider({
|
|
|
67
137
|
window.addEventListener("storage", onStorage);
|
|
68
138
|
return () => window.removeEventListener("storage", onStorage);
|
|
69
139
|
}, []);
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
if (!accessToken) {
|
|
142
|
+
if (refreshCleanupRef.current) {
|
|
143
|
+
refreshCleanupRef.current();
|
|
144
|
+
refreshCleanupRef.current = null;
|
|
145
|
+
}
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const cleanup = scheduleTokenRefresh(accessToken, (freshToken) => {
|
|
149
|
+
localStorage.setItem(STORAGE_KEYS.ACCESS_TOKEN, freshToken);
|
|
150
|
+
setToken(freshToken);
|
|
151
|
+
window.dispatchEvent(
|
|
152
|
+
new StorageEvent("storage", {
|
|
153
|
+
key: STORAGE_KEYS.ACCESS_TOKEN,
|
|
154
|
+
newValue: freshToken,
|
|
155
|
+
oldValue: accessToken
|
|
156
|
+
})
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
refreshCleanupRef.current = cleanup;
|
|
160
|
+
return () => {
|
|
161
|
+
if (cleanup) cleanup();
|
|
162
|
+
};
|
|
163
|
+
}, [accessToken]);
|
|
70
164
|
const login = useCallback((token, userData) => {
|
|
71
165
|
localStorage.setItem(STORAGE_KEYS.ACCESS_TOKEN, token);
|
|
72
166
|
localStorage.setItem(STORAGE_KEYS.USER, JSON.stringify(userData));
|
|
@@ -146,7 +240,7 @@ function useSanvikaAuth() {
|
|
|
146
240
|
}
|
|
147
241
|
|
|
148
242
|
// SanvikaAccountButton.jsx
|
|
149
|
-
import { useEffect as useEffect2, useRef, useState as useState2 } from "react";
|
|
243
|
+
import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
150
244
|
|
|
151
245
|
// #style-inject:#style-inject
|
|
152
246
|
function styleInject(css, { insertAt } = {}) {
|
|
@@ -171,7 +265,7 @@ function styleInject(css, { insertAt } = {}) {
|
|
|
171
265
|
}
|
|
172
266
|
|
|
173
267
|
// SanvikaAccountButton.css
|
|
174
|
-
styleInject("@keyframes snvk-shimmer {\n 0% {\n background-position: 200% 0;\n }\n 100% {\n background-position: -200% 0;\n }\n}\n.snvk-skeleton {\n width: clamp(72px, 20vw, 96px);\n height: clamp(34px, 8vw, 40px);\n border-radius: 8px;\n background:\n linear-gradient(\n 90deg,\n var(--skeleton-base-color, #d0d0d0) 25%,\n var(--skeleton-highlight-color, #f0f0f0) 50%,\n var(--skeleton-base-color, #d0d0d0) 75%);\n background-size: 200% 100%;\n animation: snvk-shimmer 1.4s infinite;\n display: inline-block;\n}\n.snvk-wrapper {\n position: relative;\n display: inline-block;\n}\n.snvk-guestBtn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: clamp(6px, 2vw, 8px) clamp(10px, 3vw, 16px);\n min-height: 44px;\n min-width: 44px;\n background: var(--sanvika-brand-color, #4f46e5);\n color: #ffffff;\n border: none;\n border-radius: 8px;\n font-size: clamp(13px, 3vw, 14px);\n font-weight: 600;\n cursor: pointer;\n transition: background-color 0.3s ease, color 0.3s ease;\n white-space: nowrap;\n}\n.snvk-guestBtn:hover {\n background: var(--sanvika-brand-hover, #4338ca);\n}\n.snvk-iconWrap {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n}\n.snvk-profileBtn {\n display: inline-flex;\n align-items: center;\n gap: 7px;\n padding: 5px 10px 5px 5px;\n min-height: 44px;\n background: var(--muted-bg, #f5f5f5);\n border: 1px solid var(--border-color-light, #e5e7eb);\n border-radius: 99px;\n font-size: clamp(13px, 3vw, 14px);\n font-weight: 600;\n cursor: pointer;\n color: var(--text-color, #1a1a1a);\n transition:\n background-color 0.3s ease,\n border-color 0.3s ease,\n color 0.3s ease;\n white-space: nowrap;\n outline: none;\n}\n.snvk-profileBtn:hover {\n background: var(--hover-color, #ebebeb);\n border-color: var(--border-color-dark, #d1d5db);\n}\n.snvk-profileBtn:focus-visible {\n outline: 2px solid var(--sanvika-brand-color, #4f46e5);\n outline-offset: 2px;\n}\n.snvk-avatar {\n width: 28px;\n height: 28px;\n border-radius: 50%;\n object-fit: cover;\n border: 2px solid var(--sanvika-brand-color, #4f46e5);\n flex-shrink: 0;\n}\n.snvk-textWrap {\n display: inline;\n}\n.snvk-hideTextOnMobile {\n display: none;\n}\n@media (min-width: 500px) {\n .snvk-hideTextOnMobile {\n display: inline;\n }\n}\n.snvk-dropdown {\n position: absolute;\n top: calc(100% + 8px);\n right: 0;\n min-width: clamp(200px, 60vw, 240px);\n background: var(--card-bg, #
|
|
268
|
+
styleInject("@keyframes snvk-shimmer {\n 0% {\n background-position: 200% 0;\n }\n 100% {\n background-position: -200% 0;\n }\n}\n.snvk-skeleton {\n width: clamp(72px, 20vw, 96px);\n height: clamp(34px, 8vw, 40px);\n border-radius: 8px;\n background:\n linear-gradient(\n 90deg,\n var(--skeleton-base-color, #d0d0d0) 25%,\n var(--skeleton-highlight-color, #f0f0f0) 50%,\n var(--skeleton-base-color, #d0d0d0) 75%);\n background-size: 200% 100%;\n animation: snvk-shimmer 1.4s infinite;\n display: inline-block;\n}\n.snvk-wrapper {\n position: relative;\n display: inline-block;\n}\n.snvk-guestBtn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: clamp(6px, 2vw, 8px) clamp(10px, 3vw, 16px);\n min-height: 44px;\n min-width: 44px;\n background: var(--sanvika-brand-color, #4f46e5);\n color: #ffffff;\n border: none;\n border-radius: 8px;\n font-size: clamp(13px, 3vw, 14px);\n font-weight: 600;\n cursor: pointer;\n transition: background-color 0.3s ease, color 0.3s ease;\n white-space: nowrap;\n}\n.snvk-guestBtn:hover {\n background: var(--sanvika-brand-hover, #4338ca);\n}\n.snvk-iconWrap {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n}\n.snvk-profileBtn {\n display: inline-flex;\n align-items: center;\n gap: 7px;\n padding: 5px 10px 5px 5px;\n min-height: 44px;\n background: var(--muted-bg, #f5f5f5);\n border: 1px solid var(--border-color-light, #e5e7eb);\n border-radius: 99px;\n font-size: clamp(13px, 3vw, 14px);\n font-weight: 600;\n cursor: pointer;\n color: var(--text-color, #1a1a1a);\n transition:\n background-color 0.3s ease,\n border-color 0.3s ease,\n color 0.3s ease;\n white-space: nowrap;\n outline: none;\n}\n.snvk-profileBtn:hover {\n background: var(--hover-color, #ebebeb);\n border-color: var(--border-color-dark, #d1d5db);\n}\n.snvk-profileBtn:focus-visible {\n outline: 2px solid var(--sanvika-brand-color, #4f46e5);\n outline-offset: 2px;\n}\n.snvk-avatar {\n width: 28px;\n height: 28px;\n border-radius: 50%;\n object-fit: cover;\n border: 2px solid var(--sanvika-brand-color, #4f46e5);\n flex-shrink: 0;\n}\n.snvk-textWrap {\n display: inline;\n}\n.snvk-hideTextOnMobile {\n display: none;\n}\n@media (min-width: 500px) {\n .snvk-hideTextOnMobile {\n display: inline;\n }\n}\n.snvk-dropdown {\n position: absolute;\n top: calc(100% + 8px);\n right: 0;\n min-width: clamp(200px, 60vw, 240px);\n background: var(--card-bg, #f3f3f3);\n border: 1px solid var(--border-color-light, #cccccc);\n border-radius: 12px;\n box-shadow: 0 8px 24px var(--shadow-color, rgba(0, 0, 0, 0.2));\n z-index: 9999;\n overflow: hidden;\n transition: background-color 0.3s ease, border-color 0.3s ease;\n}\n.snvk-dropdownHeader {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 14px 16px;\n background: var(--section-bg, #ebebeb);\n transition: background-color 0.3s ease;\n}\n.snvk-dropdownAvatar {\n width: 38px;\n height: 38px;\n border-radius: 50%;\n object-fit: cover;\n border: 2px solid var(--sanvika-brand-color, #4f46e5);\n flex-shrink: 0;\n}\n.snvk-dropdownName {\n font-size: clamp(13px, 3vw, 14px);\n font-weight: 800;\n color: var(--text-color, #000000);\n line-height: 1.3;\n transition: color 0.3s ease;\n}\n.snvk-dropdownMobile {\n font-size: clamp(11px, 2.5vw, 12px);\n color: var(--secondary-text-color, #333333);\n margin-top: 2px;\n transition: color 0.3s ease;\n font-weight: 700;\n}\n.snvk-divider {\n height: 1px;\n background: var(--muted-border, #d8d8d8);\n transition: background-color 0.3s ease;\n}\n.snvk-dropdownItem {\n display: flex;\n align-items: center;\n gap: 10px;\n width: 100%;\n padding: 11px 16px;\n min-height: 44px;\n background: none;\n border: none;\n font-size: clamp(13px, 3vw, 14px);\n color: var(--text-color, #000000);\n font-weight: 600;\n cursor: pointer;\n text-decoration: none;\n text-align: left;\n transition: background-color 0.3s ease, color 0.3s ease;\n}\n.snvk-dropdownItem:hover {\n background: var(--hover-color, #e8e8e8);\n}\n.snvk-logoutItem {\n color: var(--error-color, #c0392b);\n}\n.snvk-logoutItem:hover {\n background: var(--error-bg, #fff5f5);\n}\n.snvk-deleteItem {\n color: var(--warning-color, #d97706);\n}\n.snvk-deleteItem:hover {\n background: var(--warning-bg, #fffbf0);\n}\n");
|
|
175
269
|
|
|
176
270
|
// SanvikaAccountButton.jsx
|
|
177
271
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
@@ -293,7 +387,7 @@ function SanvikaAccountButton({
|
|
|
293
387
|
const [dropdownOpen, setDropdownOpen] = useState2(false);
|
|
294
388
|
const [imgError, setImgError] = useState2(false);
|
|
295
389
|
const [prevImage, setPrevImage] = useState2(user == null ? void 0 : user.image);
|
|
296
|
-
const wrapperRef =
|
|
390
|
+
const wrapperRef = useRef2(null);
|
|
297
391
|
if ((user == null ? void 0 : user.image) !== prevImage) {
|
|
298
392
|
setPrevImage(user == null ? void 0 : user.image);
|
|
299
393
|
setImgError(false);
|
|
@@ -417,7 +511,20 @@ function SanvikaAccountButton({
|
|
|
417
511
|
/* @__PURE__ */ jsxs(
|
|
418
512
|
"a",
|
|
419
513
|
{
|
|
420
|
-
href:
|
|
514
|
+
href: "https://accounts.sanvikaproduction.com/dashboard/user-profile",
|
|
515
|
+
className: "snvk-dropdownItem",
|
|
516
|
+
role: "menuitem",
|
|
517
|
+
onClick: () => setDropdownOpen(false),
|
|
518
|
+
children: [
|
|
519
|
+
/* @__PURE__ */ jsx2(UserIcon, { size: 15 }),
|
|
520
|
+
/* @__PURE__ */ jsx2("span", { children: "View Profile" })
|
|
521
|
+
]
|
|
522
|
+
}
|
|
523
|
+
),
|
|
524
|
+
/* @__PURE__ */ jsxs(
|
|
525
|
+
"a",
|
|
526
|
+
{
|
|
527
|
+
href: onboardingPath && user.status === "onboarding" ? onboardingPath : dashboardPath || "https://accounts.sanvikaproduction.com/dashboard",
|
|
421
528
|
className: "snvk-dropdownItem",
|
|
422
529
|
role: "menuitem",
|
|
423
530
|
onClick: () => setDropdownOpen(false),
|
|
@@ -430,7 +537,9 @@ function SanvikaAccountButton({
|
|
|
430
537
|
/* @__PURE__ */ jsxs(
|
|
431
538
|
"a",
|
|
432
539
|
{
|
|
433
|
-
href:
|
|
540
|
+
href: `https://accounts.sanvikaproduction.com/account/delete?token=${encodeURIComponent(
|
|
541
|
+
(user == null ? void 0 : user.accessToken) || ""
|
|
542
|
+
)}`,
|
|
434
543
|
className: "snvk-dropdownItem snvk-deleteItem",
|
|
435
544
|
role: "menuitem",
|
|
436
545
|
onClick: () => setDropdownOpen(false),
|
|
@@ -466,5 +575,7 @@ export {
|
|
|
466
575
|
SanvikaAccountButton,
|
|
467
576
|
SanvikaAuthContext,
|
|
468
577
|
SanvikaAuthProvider,
|
|
578
|
+
decodeToken,
|
|
579
|
+
ensureTokenFresh,
|
|
469
580
|
useSanvikaAuth
|
|
470
581
|
};
|