fix menu tax in data and fix session when multiple user login

This commit is contained in:
arifal hidayat
2025-08-07 00:51:46 +07:00
parent 0abf278aa3
commit af05a39a82
13 changed files with 1209 additions and 36 deletions

View File

@@ -171,16 +171,23 @@ class ApiTokenManager {
try {
const response = await fetch(url, mergedOptions);
// If unauthorized, try to generate new token
if (response.status === 401 && this.hasToken()) {
console.log('Token expired, generating new token...');
const newToken = await this.generateToken();
if (newToken) {
// Retry request with new token
mergedOptions.headers.Authorization = `Bearer ${newToken}`;
return fetch(url, mergedOptions);
// If unauthorized, check if it's a session issue
if (response.status === 401) {
if (this.hasToken()) {
console.log('Token expired, generating new token...');
const newToken = await this.generateToken();
if (newToken) {
// Retry request with new token
mergedOptions.headers.Authorization = `Bearer ${newToken}`;
return fetch(url, mergedOptions);
}
}
// If still 401, it might be a session issue
console.log('Session invalid, redirecting to login...');
this.handleSessionInvalid();
return response;
}
return response;
@@ -189,6 +196,33 @@ class ApiTokenManager {
throw error;
}
}
handleSessionInvalid() {
// Show notification
this.showNotification('Session Anda telah berakhir. Silakan login ulang.', 'warning');
// Redirect to login after 3 seconds
setTimeout(() => {
window.location.href = '/login';
}, 3000);
}
showNotification(message, type = 'info') {
// Check if notification library exists
if (typeof toastr !== 'undefined') {
toastr[type](message);
} else if (typeof Swal !== 'undefined') {
Swal.fire({
title: 'Peringatan',
text: message,
icon: type,
confirmButtonText: 'OK'
});
} else {
// Fallback to alert
alert(message);
}
}
}
// Export singleton instance

View File

@@ -0,0 +1,337 @@
/**
* API Token Web Validator
* Menangani validasi token API untuk web requests dan auto-logout
*/
class ApiTokenWebValidator {
constructor() {
this.isLoggingOut = false;
this.checkInterval = null;
this.lastCheckTime = 0;
this.consecutiveErrors = 0;
this.maxConsecutiveErrors = 3;
this.init();
}
init() {
console.log("API Token Web Validator initialized");
// Start periodic validation
this.startPeriodicValidation();
// Intercept all AJAX requests
this.interceptAjaxRequests();
// Listen for page visibility changes
document.addEventListener("visibilitychange", () => {
if (!document.hidden) {
this.validateToken();
}
});
// Listen for window focus
window.addEventListener("focus", () => {
this.validateToken();
});
}
startPeriodicValidation() {
// Check token validity every 15 seconds
this.checkInterval = setInterval(() => {
this.validateToken();
}, 15000);
}
stopPeriodicValidation() {
if (this.checkInterval) {
clearInterval(this.checkInterval);
this.checkInterval = null;
}
}
async validateToken() {
// Prevent multiple simultaneous checks
if (this.isLoggingOut) {
return;
}
// Prevent checking too frequently
const now = Date.now();
if (now - this.lastCheckTime < 3000) {
return;
}
this.lastCheckTime = now;
try {
const response = await fetch("/api/check-session", {
method: "GET",
headers: {
"Content-Type": "application/json",
"X-Requested-With": "XMLHttpRequest",
"Cache-Control": "no-cache",
},
credentials: "include",
});
if (response.status === 401) {
console.log("Token validation failed: 401 Unauthorized");
this.consecutiveErrors++;
if (this.consecutiveErrors >= this.maxConsecutiveErrors) {
this.handleTokenInvalid(
"Token API tidak valid. User lain mungkin telah login."
);
}
} else if (response.status === 200) {
// Reset consecutive errors on successful response
this.consecutiveErrors = 0;
const data = await response.json();
if (!data.valid) {
console.log("Token validation failed: Session invalid");
this.handleTokenInvalid(
"Session tidak valid. Silakan login ulang."
);
}
}
} catch (error) {
console.error("Token validation error:", error);
this.consecutiveErrors++;
if (this.consecutiveErrors >= this.maxConsecutiveErrors) {
this.handleTokenInvalid(
"Terjadi kesalahan koneksi. Silakan login ulang."
);
}
}
}
interceptAjaxRequests() {
// Intercept fetch requests
const originalFetch = window.fetch;
const validator = this;
window.fetch = async function (...args) {
try {
const response = await originalFetch(...args);
// Check if response is 401 and it's an API call
if (response.status === 401) {
const url = args[0];
if (typeof url === "string" && url.includes("/api/")) {
console.log("401 detected in API fetch request:", url);
validator.handleApiError401(response, url);
}
}
return response;
} catch (error) {
console.error("Fetch request failed:", error);
throw error;
}
};
// Intercept XMLHttpRequest
const originalXHRSend = XMLHttpRequest.prototype.send;
const originalXHROpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function (...args) {
this._url = args[1];
return originalXHROpen.apply(this, args);
};
XMLHttpRequest.prototype.send = function (...args) {
const xhr = this;
const originalOnReadyStateChange = xhr.onreadystatechange;
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 401) {
if (xhr._url && xhr._url.includes("/api/")) {
console.log(
"401 detected in API XHR request:",
xhr._url
);
validator.handleApiError401(null, xhr._url);
}
}
if (originalOnReadyStateChange) {
originalOnReadyStateChange.apply(xhr, arguments);
}
};
return originalXHRSend.apply(this, args);
};
// Intercept jQuery AJAX if available
if (typeof $ !== "undefined" && $.ajaxSetup) {
$(document).ajaxError(function (event, xhr, settings, thrownError) {
if (
xhr.status === 401 &&
settings.url &&
settings.url.includes("/api/")
) {
console.log(
"401 detected in jQuery AJAX request:",
settings.url
);
validator.handleApiError401(null, settings.url);
}
});
}
}
handleApiError401(response, url) {
if (this.isLoggingOut) {
return;
}
console.log(`API 401 Error detected on ${url}`);
// Increment consecutive errors
this.consecutiveErrors++;
// If we get multiple 401s, likely token is invalid
if (this.consecutiveErrors >= 2) {
this.handleTokenInvalid(
"Token API tidak valid. User lain telah login atau session berakhir."
);
}
}
handleTokenInvalid(message) {
if (this.isLoggingOut) {
return;
}
this.isLoggingOut = true;
this.stopPeriodicValidation();
console.log("Handling token invalid:", message);
// Show notification
this.showNotification(message, "warning");
// Clear any stored data
this.clearStoredData();
// Redirect to login after 3 seconds
setTimeout(() => {
this.forceLogout();
}, 3000);
}
showNotification(message, type = "info") {
// Try different notification libraries
if (typeof toastr !== "undefined") {
toastr[type](message);
} else if (typeof Swal !== "undefined") {
Swal.fire({
title: "Peringatan Session",
text: message,
icon: type,
confirmButtonText: "OK",
allowOutsideClick: false,
timer: 5000,
timerProgressBar: true,
});
} else {
// Create custom notification
this.createCustomNotification(message, type);
}
}
createCustomNotification(message, type) {
const notification = document.createElement("div");
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: ${type === "warning" ? "#ffc107" : "#007bff"};
color: white;
padding: 15px 20px;
border-radius: 5px;
z-index: 9999;
max-width: 400px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
font-family: Arial, sans-serif;
font-size: 14px;
`;
notification.innerHTML = `
<strong>Peringatan!</strong><br>
${message}
`;
document.body.appendChild(notification);
// Remove after 8 seconds
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, 8000);
}
clearStoredData() {
// Clear localStorage
try {
localStorage.clear();
} catch (e) {
console.warn("Could not clear localStorage:", e);
}
// Clear sessionStorage
try {
sessionStorage.clear();
} catch (e) {
console.warn("Could not clear sessionStorage:", e);
}
}
forceLogout() {
console.log("Forcing logout...");
// Try to logout via API first
fetch("/logout", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-TOKEN":
document
.querySelector('meta[name="csrf-token"]')
?.getAttribute("content") || "",
"X-Requested-With": "XMLHttpRequest",
},
credentials: "include",
})
.then(() => {
window.location.href = "/login";
})
.catch(() => {
// Force redirect even if logout fails
window.location.href = "/login";
});
}
// Method untuk manual reset
reset() {
this.consecutiveErrors = 0;
this.isLoggingOut = false;
this.lastCheckTime = 0;
}
// Method untuk manual logout
logout() {
this.handleTokenInvalid("Manual logout requested");
}
}
// Initialize when DOM is ready
document.addEventListener("DOMContentLoaded", () => {
window.apiTokenWebValidator = new ApiTokenWebValidator();
});
// Export for module usage
if (typeof module !== "undefined" && module.exports) {
module.exports = ApiTokenWebValidator;
}

View File

@@ -0,0 +1,264 @@
/**
* Multi-User Session Handler
* Menangani kasus ketika multiple user login dan session conflict
*/
class MultiUserSessionHandler {
constructor() {
this.checkInterval = null;
this.lastCheckTime = 0;
this.isChecking = false;
this.init();
}
init() {
console.log("Multi-User Session Handler initialized");
// Check session setiap 10 detik
this.startPeriodicCheck();
// Check session ketika tab menjadi aktif
document.addEventListener("visibilitychange", () => {
if (!document.hidden) {
this.checkSession();
}
});
// Check session ketika window focus
window.addEventListener("focus", () => {
this.checkSession();
});
// Check session ketika user melakukan interaksi
document.addEventListener("click", () => {
this.checkSession();
});
// Check session ketika ada AJAX request
this.interceptAjaxRequests();
}
startPeriodicCheck() {
this.checkInterval = setInterval(() => {
this.checkSession();
}, 10000); // 10 detik
}
stopPeriodicCheck() {
if (this.checkInterval) {
clearInterval(this.checkInterval);
this.checkInterval = null;
}
}
async checkSession() {
// Prevent multiple simultaneous checks
if (this.isChecking) {
return;
}
// Prevent checking too frequently
const now = Date.now();
if (now - this.lastCheckTime < 5000) {
// 5 detik minimum interval
return;
}
this.isChecking = true;
this.lastCheckTime = now;
try {
const response = await fetch("/api/check-session", {
method: "GET",
headers: {
"Content-Type": "application/json",
"X-Requested-With": "XMLHttpRequest",
"Cache-Control": "no-cache",
},
credentials: "include",
});
if (response.status === 401) {
console.log("Session invalid detected, logging out...");
this.handleSessionInvalid();
} else if (response.status === 200) {
const data = await response.json();
if (!data.valid) {
console.log("Session validation failed, logging out...");
this.handleSessionInvalid();
}
}
} catch (error) {
console.error("Session check failed:", error);
} finally {
this.isChecking = false;
}
}
interceptAjaxRequests() {
// Intercept fetch requests
const originalFetch = window.fetch;
window.fetch = async (...args) => {
try {
const response = await originalFetch(...args);
// Check if response is 401
if (response.status === 401) {
console.log(
"401 detected in fetch request, checking session..."
);
this.checkSession();
}
return response;
} catch (error) {
console.error("Fetch request failed:", error);
throw error;
}
};
// Intercept XMLHttpRequest
const originalXHROpen = XMLHttpRequest.prototype.open;
const originalXHRSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function (...args) {
this._url = args[1];
return originalXHROpen.apply(this, args);
};
XMLHttpRequest.prototype.send = function (...args) {
const xhr = this;
const originalOnReadyStateChange = xhr.onreadystatechange;
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 401) {
console.log(
"401 detected in XHR request, checking session..."
);
window.multiUserSessionHandler.checkSession();
}
if (originalOnReadyStateChange) {
originalOnReadyStateChange.apply(xhr, arguments);
}
};
return originalXHRSend.apply(this, args);
};
}
handleSessionInvalid() {
this.stopPeriodicCheck();
// Show notification
this.showNotification(
"Session Anda telah berakhir karena user lain login. Silakan login ulang.",
"warning"
);
// Clear any stored data
this.clearStoredData();
// Redirect to login after 2 seconds
setTimeout(() => {
window.location.href = "/login";
}, 2000);
}
showNotification(message, type = "info") {
// Try different notification libraries
if (typeof toastr !== "undefined") {
toastr[type](message);
} else if (typeof Swal !== "undefined") {
Swal.fire({
title: "Peringatan Session",
text: message,
icon: type,
confirmButtonText: "OK",
allowOutsideClick: false,
});
} else if (typeof alert !== "undefined") {
alert(message);
} else {
// Create custom notification
this.createCustomNotification(message, type);
}
}
createCustomNotification(message, type) {
const notification = document.createElement("div");
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: ${type === "warning" ? "#ffc107" : "#007bff"};
color: white;
padding: 15px 20px;
border-radius: 5px;
z-index: 9999;
max-width: 400px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
`;
notification.textContent = message;
document.body.appendChild(notification);
// Remove after 5 seconds
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, 5000);
}
clearStoredData() {
// Clear localStorage
try {
localStorage.clear();
} catch (e) {
console.warn("Could not clear localStorage:", e);
}
// Clear sessionStorage
try {
sessionStorage.clear();
} catch (e) {
console.warn("Could not clear sessionStorage:", e);
}
}
// Method untuk manual logout
logout() {
this.stopPeriodicCheck();
fetch("/logout", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-TOKEN":
document
.querySelector('meta[name="csrf-token"]')
?.getAttribute("content") || "",
"X-Requested-With": "XMLHttpRequest",
},
credentials: "include",
})
.then(() => {
this.clearStoredData();
window.location.href = "/login";
})
.catch(() => {
this.clearStoredData();
window.location.href = "/login";
});
}
}
// Initialize when DOM is ready
document.addEventListener("DOMContentLoaded", () => {
window.multiUserSessionHandler = new MultiUserSessionHandler();
});
// Export for module usage
if (typeof module !== "undefined" && module.exports) {
module.exports = MultiUserSessionHandler;
}

View File

@@ -0,0 +1,126 @@
/**
* Session Manager untuk menangani multi-user session
*/
class SessionManager {
constructor() {
this.checkInterval = null;
this.init();
}
init() {
// Check session setiap 30 detik
this.startSessionCheck();
// Listen untuk visibility change (tab focus/blur)
document.addEventListener("visibilitychange", () => {
if (!document.hidden) {
this.checkSession();
}
});
// Listen untuk storage events (multi-tab)
window.addEventListener("storage", (e) => {
if (e.key === "session_invalid") {
this.handleSessionInvalid();
}
});
}
startSessionCheck() {
this.checkInterval = setInterval(() => {
this.checkSession();
}, 30000); // 30 detik
}
stopSessionCheck() {
if (this.checkInterval) {
clearInterval(this.checkInterval);
this.checkInterval = null;
}
}
async checkSession() {
try {
const response = await fetch("/api/check-session", {
method: "GET",
headers: {
"Content-Type": "application/json",
"X-Requested-With": "XMLHttpRequest",
},
credentials: "include",
});
if (response.status === 401) {
this.handleSessionInvalid();
}
} catch (error) {
console.error("Session check failed:", error);
}
}
handleSessionInvalid() {
this.stopSessionCheck();
// Show notification
this.showNotification(
"Session Anda telah berakhir. Silakan login ulang.",
"warning"
);
// Redirect to login after 3 seconds
setTimeout(() => {
window.location.href = "/login";
}, 3000);
}
showNotification(message, type = "info") {
// Check if notification library exists (like Toastr, SweetAlert, etc.)
if (typeof toastr !== "undefined") {
toastr[type](message);
} else if (typeof Swal !== "undefined") {
Swal.fire({
title: "Peringatan",
text: message,
icon: type,
confirmButtonText: "OK",
});
} else {
// Fallback to alert
alert(message);
}
}
// Method untuk logout manual
logout() {
this.stopSessionCheck();
fetch("/logout", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-TOKEN":
document
.querySelector('meta[name="csrf-token"]')
?.getAttribute("content") || "",
"X-Requested-With": "XMLHttpRequest",
},
credentials: "include",
})
.then(() => {
window.location.href = "/login";
})
.catch(() => {
window.location.href = "/login";
});
}
}
// Initialize session manager when DOM is ready
document.addEventListener("DOMContentLoaded", () => {
window.sessionManager = new SessionManager();
});
// Export for module usage
if (typeof module !== "undefined" && module.exports) {
module.exports = SessionManager;
}