membros-react-sdk
Version:
React authentication library for Membros platform with subscription management and plan-based access control
300 lines • 12.9 kB
JavaScript
"use client";
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { createContext, useContext, useEffect, useState, } from "react";
import { setCookie, destroyCookie, parseCookies } from "./utils/cookies.esm.js";
import { Toaster, toast } from "sonner";
import { MEMBROS_API_URL } from "./constants.esm.js";
const AuthContext = createContext(undefined);
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error("useAuth must be used within an MembrosProvider");
}
return context;
};
export const signOut = () => {
destroyCookie(null, "nextauth.token");
destroyCookie(null, "nextauth.refreshToken");
window.location.reload();
};
export const MembrosProvider = ({ children, clientId, authorizationParams, }) => {
const [user, setUser] = useState(null);
const [token, setToken] = useState(null);
const [internalIsLoadingUser, setInternalIsLoadingUser] = useState(true);
const [internalIsLoadingSubscriptions, setInternalIsLoadingSubscriptions] = useState(true);
const [error, setError] = useState(null);
const [adimplent, setAdimplent] = useState(false);
const [originalUser, setOriginalUser] = useState(null);
const [userSubscriptions, setUserSubscriptions] = useState([]);
const [isLoggingOut, setIsLoggingOut] = useState(false);
const isAuthenticated = !!user;
const isLoading = internalIsLoadingUser || internalIsLoadingSubscriptions;
useEffect(() => {
const loadUserFromCookies = async () => {
setInternalIsLoadingUser(true);
setInternalIsLoadingSubscriptions(true);
// First check for authorization code in URL (for redirect flow)
const urlParams = new URLSearchParams(window.location.search);
const authCode = urlParams.get('code');
if (authCode) {
console.log("Found authorization code in URL, processing...");
await login(authCode);
// Clean up URL
const newUrl = window.location.pathname;
window.history.replaceState({}, document.title, newUrl);
return;
}
// If no auth code, check for existing token in cookies
const { "nextauth.token": accessToken } = parseCookies();
if (accessToken) {
await loadUserByToken(accessToken);
}
else {
setInternalIsLoadingUser(false);
setInternalIsLoadingSubscriptions(false);
}
};
loadUserFromCookies();
}, []);
const loadUserSubscriptions = async (email, currentToken) => {
setInternalIsLoadingSubscriptions(true);
try {
const response = await fetch(`${MEMBROS_API_URL}/subscription/account/email/${email}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${currentToken}`,
},
});
if (response.status === 404) {
setUserSubscriptions([]);
return;
}
const subscriptions = await response.json();
setUserSubscriptions(subscriptions);
}
catch (error) {
console.error("An unexpected error occurred while loading subscriptions", error);
setUserSubscriptions([]);
}
finally {
setInternalIsLoadingSubscriptions(false);
}
};
const loadUserByToken = async (accessToken) => {
try {
setError(null);
setInternalIsLoadingUser(true);
setToken(accessToken);
setCookie(null, "nextauth.token", accessToken, {
path: "/",
maxAge: 60 * 60 * 24 * 30, // 30 days in seconds
});
const response = await fetch(`${MEMBROS_API_URL}/whoami`, {
headers: { Authorization: `Bearer ${accessToken}` },
});
if (response.status === 401) {
signOut();
setInternalIsLoadingUser(false);
setInternalIsLoadingSubscriptions(false);
return;
}
if (response.ok) {
const userData = await response.json();
setUser(userData);
setInternalIsLoadingUser(false);
await loadUserSubscriptions(userData.email, accessToken);
}
else {
console.error("Failed to load user data");
setUser(null);
setToken(null);
destroyCookie(null, "nextauth.token");
destroyCookie(null, "nextauth.refreshToken");
setInternalIsLoadingUser(false);
setInternalIsLoadingSubscriptions(false);
}
}
catch (error) {
console.error("Error in loadUserByToken:", error);
setError(error);
setUser(null);
setToken(null);
destroyCookie(null, "nextauth.token");
destroyCookie(null, "nextauth.refreshToken");
setInternalIsLoadingUser(false);
setInternalIsLoadingSubscriptions(false);
}
};
const loginWithRedirect = async (options) => {
var _a;
const redirectUri = ((_a = options === null || options === void 0 ? void 0 : options.authorizationParams) === null || _a === void 0 ? void 0 : _a.redirect_uri) ||
(options === null || options === void 0 ? void 0 : options.redirectUri) ||
(authorizationParams === null || authorizationParams === void 0 ? void 0 : authorizationParams.redirect_uri) ||
window.location.origin;
// Use the OAuth2 page with redirect flow
const authUrl = `http://localhost:3003/oauth2/${clientId}?flow=redirect&redirect_uri=${encodeURIComponent(redirectUri)}`;
window.location.href = authUrl;
};
const loginWithPopup = async (options) => {
return new Promise((resolve, reject) => {
const redirectOrigin = window.location.origin;
// Use the OAuth2 page with popup flow
const authUrl = `http://localhost:3003/oauth2/${clientId}?flow=popup&redirect_origin=${encodeURIComponent(redirectOrigin)}`;
const popup = window.open(authUrl, "auth-popup", "width=500,height=600");
const checkClosed = setInterval(() => {
if (popup === null || popup === void 0 ? void 0 : popup.closed) {
clearInterval(checkClosed);
reject(new Error("Popup was closed"));
}
}, 1000);
const messageHandler = (event) => {
// Allow localhost for debugging
if (event.origin !== "http://localhost:3003")
return;
if (event.data.type === "oauth" && event.data.code) {
clearInterval(checkClosed);
popup === null || popup === void 0 ? void 0 : popup.close();
window.removeEventListener("message", messageHandler);
login(event.data.code).then(() => resolve()).catch(reject);
}
};
window.addEventListener("message", messageHandler);
});
};
const getAccessTokenSilently = async (options) => {
if (!token) {
throw new Error("No access token available. User might need to log in again.");
}
return token;
};
const login = async (authorizationCode) => {
try {
// For debugging with mock codes, simulate token exchange
if (authorizationCode.startsWith("auth_code_")) {
// Simulate a successful token exchange
const mockToken = `mock_token_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const mockRefreshToken = `mock_refresh_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
setCookie(null, "nextauth.token", mockToken, {
path: "/",
maxAge: 60 * 60 * 24 * 30, // 30 days in seconds
});
setCookie(null, "nextauth.refreshToken", mockRefreshToken, {
path: "/",
maxAge: 60 * 60 * 24 * 30, // 30 days in seconds
});
// Mock user data for testing
const mockUser = {
id: "mock-user-id",
name: "Mock User",
email: "mock@example.com",
plano: "vestibulando",
};
setUser(mockUser);
setToken(mockToken);
setInternalIsLoadingUser(false);
setInternalIsLoadingSubscriptions(false);
toast.success("Login Successful", { description: "You are now logged in (mock mode)." });
return;
}
// Real token exchange for production
const res = await fetch(`${MEMBROS_API_URL}/user/auth/token`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ authorization_code: authorizationCode }),
});
const responseData = await res.json();
if (res.ok && responseData.access_token) {
setCookie(null, "nextauth.token", responseData.access_token, {
path: "/",
maxAge: 60 * 60 * 24 * 30, // 30 days in seconds
});
setCookie(null, "nextauth.refreshToken", responseData.refresh_token, {
path: "/",
maxAge: 60 * 60 * 24 * 30, // 30 days in seconds
});
await loadUserByToken(responseData.access_token);
toast.success("Login Successful", { description: "You are now logged in." });
}
else {
toast.error("Login Failed", { description: responseData.message || "Failed to retrieve token." });
}
}
catch (error) {
console.error("Login error:", error);
toast.error("Server Error", { description: "An error occurred while fetching the token." });
}
};
const logout = (options) => {
var _a;
setIsLoggingOut(true);
destroyCookie(null, "nextauth.token");
destroyCookie(null, "nextauth.refreshToken");
setUser(null);
setToken(null);
setUserSubscriptions([]);
setOriginalUser(null);
setInternalIsLoadingUser(false);
setInternalIsLoadingSubscriptions(false);
const returnTo = ((_a = options === null || options === void 0 ? void 0 : options.logoutParams) === null || _a === void 0 ? void 0 : _a.returnTo) ||
(options === null || options === void 0 ? void 0 : options.returnTo) ||
window.location.origin;
if (typeof window !== "undefined") {
window.location.href = returnTo;
}
};
const hasActivePlan = (planIds) => {
if (!userSubscriptions || userSubscriptions.length === 0) {
return false;
}
if (!planIds || planIds.length === 0) {
return userSubscriptions.some(sub => sub.status === 'active');
}
for (const subscription of userSubscriptions) {
const subscriptionPlanId = String(subscription.plan.id);
if (planIds.includes(subscriptionPlanId)) {
if (subscription.status === 'active') {
return true;
}
}
}
return false;
};
const overwriteUser = (newUser) => {
if (!originalUser) {
setOriginalUser(user);
}
setUser(newUser);
if (typeof window !== "undefined") {
window.location.href = "/";
}
};
const revertToOriginalUser = () => {
if (originalUser) {
setUser(originalUser);
setOriginalUser(null);
}
};
return (_jsxs(AuthContext.Provider, { value: {
user,
isAuthenticated,
isLoading,
isLoggingOut,
error,
loginWithRedirect,
loginWithPopup,
logout,
getAccessTokenSilently,
originalUser,
token,
login,
loadUserByToken,
adimplent,
overwriteUser,
revertToOriginalUser,
publicKey: clientId,
hasActivePlan,
userSubscriptions,
}, children: [_jsx(Toaster, { richColors: true }), children] }));
};
//# sourceMappingURL=AuthContext.js.map