chore: refine demo mode
This commit is contained in:
parent
d19b5524c6
commit
18284f7483
277
app.js
277
app.js
@ -3,6 +3,7 @@ const rateLimit = require('express-rate-limit');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const bodyParser = require('body-parser');
|
const bodyParser = require('body-parser');
|
||||||
const API = require('./src/js/index.js');
|
const API = require('./src/js/index.js');
|
||||||
|
const demoTracker = require('./src/js/demoTracker.js');
|
||||||
const { logger } = require('./src/js/logger.js');
|
const { logger } = require('./src/js/logger.js');
|
||||||
const favicon = require('serve-favicon');
|
const favicon = require('serve-favicon');
|
||||||
const app = express();
|
const app = express();
|
||||||
@ -27,156 +28,16 @@ app.use(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
// app.use(express.raw({ type: 'application/json', limit: '10mb' }));
|
// app.use(express.raw({ type: 'application/json', limit: '10mb' }));
|
||||||
|
app.use(demoTracker.demoModeMiddleware);
|
||||||
|
|
||||||
|
// Set up demo mode cleanup interval
|
||||||
|
setInterval(
|
||||||
|
() => demoTracker.demoModeRequestTracker.cleanupExpiredEntries(),
|
||||||
|
3600000
|
||||||
|
); // Clean up every hour
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
let DEFAULT_SSO_TOKEN = '';
|
|
||||||
|
|
||||||
// Try to read the token from a file
|
|
||||||
try {
|
|
||||||
const tokenPath = path.join(__dirname, 'token.txt');
|
|
||||||
if (fs.existsSync(tokenPath)) {
|
|
||||||
DEFAULT_SSO_TOKEN = fs.readFileSync(tokenPath, 'utf8').trim();
|
|
||||||
logger.info('Default SSO token loaded successfully from token.txt');
|
|
||||||
} else {
|
|
||||||
logger.warn('token.txt not found, demo mode may not function correctly');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error loading token file:', { error: error.message });
|
|
||||||
}
|
|
||||||
|
|
||||||
setInterval(() => demoModeRequestTracker.cleanupExpiredEntries(), 3600000); // Clean up every hour
|
|
||||||
|
|
||||||
// Configure rate limiting for demo mode
|
|
||||||
const demoModeRequestTracker = {
|
|
||||||
requests: new Map(),
|
|
||||||
maxRequestsPerHour: 20, // Adjust as needed
|
|
||||||
resetInterval: 60 * 60 * 1000, // 1 hour in milliseconds
|
|
||||||
|
|
||||||
isLimited: function (ip) {
|
|
||||||
const now = Date.now();
|
|
||||||
const userRequests = this.requests.get(ip) || {
|
|
||||||
count: 0,
|
|
||||||
resetTime: now + this.resetInterval,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Reset counter if the reset time has passed
|
|
||||||
if (now > userRequests.resetTime) {
|
|
||||||
userRequests.count = 0;
|
|
||||||
userRequests.resetTime = now + this.resetInterval;
|
|
||||||
}
|
|
||||||
|
|
||||||
return userRequests.count >= this.maxRequestsPerHour;
|
|
||||||
},
|
|
||||||
|
|
||||||
incrementCounter: function (ip) {
|
|
||||||
const now = Date.now();
|
|
||||||
const userRequests = this.requests.get(ip) || {
|
|
||||||
count: 0,
|
|
||||||
resetTime: now + this.resetInterval,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Reset counter if the reset time has passed
|
|
||||||
if (now > userRequests.resetTime) {
|
|
||||||
userRequests.count = 1; // Start with 1 for this request
|
|
||||||
userRequests.resetTime = now + this.resetInterval;
|
|
||||||
} else {
|
|
||||||
userRequests.count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.requests.set(ip, userRequests);
|
|
||||||
return userRequests.count;
|
|
||||||
},
|
|
||||||
|
|
||||||
cleanupExpiredEntries: function () {
|
|
||||||
const now = Date.now();
|
|
||||||
for (const [ip, data] of this.requests.entries()) {
|
|
||||||
if (now > data.resetTime) {
|
|
||||||
// Either remove entries completely or reset them to 0
|
|
||||||
this.requests.delete(ip);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Demo mode middleware
|
|
||||||
const demoModeMiddleware = (req, res, next) => {
|
|
||||||
// Skip non-API routes
|
|
||||||
if (!req.path.startsWith('/api/')) {
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get client IP for tracking
|
|
||||||
// const clientIP = req.headers['x-forwarded-for'] || req.ip || req.connection.remoteAddress;
|
|
||||||
const clientIP =
|
|
||||||
req.headers['cf-connecting-ip'] ||
|
|
||||||
req.headers['x-forwarded-for']?.split(',')[0] ||
|
|
||||||
req.ip ||
|
|
||||||
req.connection.remoteAddress;
|
|
||||||
|
|
||||||
// Check if demo mode is active
|
|
||||||
if (DEFAULT_SSO_TOKEN) {
|
|
||||||
// For API endpoints, check rate limit in demo mode
|
|
||||||
if (demoModeRequestTracker.isLimited(clientIP)) {
|
|
||||||
const userRequests = demoModeRequestTracker.requests.get(clientIP);
|
|
||||||
const resetTimeMs = userRequests.resetTime - Date.now();
|
|
||||||
|
|
||||||
// Format time as HH:MM:SS
|
|
||||||
const hours = Math.floor(resetTimeMs / (60 * 60 * 1000));
|
|
||||||
const minutes = Math.floor(
|
|
||||||
(resetTimeMs % (60 * 60 * 1000)) / (60 * 1000)
|
|
||||||
);
|
|
||||||
const seconds = Math.floor((resetTimeMs % (60 * 1000)) / 1000);
|
|
||||||
const timeFormat = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
|
||||||
|
|
||||||
logger.warn(`Demo mode rate limit exceeded for IP: ${clientIP}`);
|
|
||||||
return res.status(429).json({
|
|
||||||
status: 'error',
|
|
||||||
message: `Please try again in ${timeFormat} or use your own SSO token.`,
|
|
||||||
timestamp: global.Utils.toIsoString(new Date()),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Increment the counter
|
|
||||||
// const count = demoModeRequestTracker.incrementCounter(clientIP);
|
|
||||||
// logger.debug(`Demo mode request count for ${clientIP}: ${count}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
next();
|
|
||||||
};
|
|
||||||
|
|
||||||
function incrementDemoCounter(req, ssoToken) {
|
|
||||||
if (ssoToken === DEFAULT_SSO_TOKEN) {
|
|
||||||
const clientIP =
|
|
||||||
req.headers['cf-connecting-ip'] ||
|
|
||||||
req.headers['x-forwarded-for']?.split(',')[0] ||
|
|
||||||
req.ip ||
|
|
||||||
req.connection.remoteAddress;
|
|
||||||
|
|
||||||
const count = demoModeRequestTracker.incrementCounter(clientIP);
|
|
||||||
logger.debug(`Demo mode request count for ${clientIP}: ${count}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
app.use(demoModeMiddleware);
|
|
||||||
app.use((req, res, next) => {
|
|
||||||
if (req.query.demo === 'true' || req.query.demo === '1') {
|
|
||||||
// Enable demo mode for this session if it's not already enabled
|
|
||||||
if (!req.session) {
|
|
||||||
req.session = {};
|
|
||||||
}
|
|
||||||
req.session.forceDemoMode = true;
|
|
||||||
logger.info('Demo mode enabled via URL parameter');
|
|
||||||
} else if (req.query.demo === 'false' || req.query.demo === '0') {
|
|
||||||
// Disable demo mode for this session
|
|
||||||
if (req.session) {
|
|
||||||
req.session.forceDemoMode = false;
|
|
||||||
}
|
|
||||||
logger.info('Demo mode disabled via URL parameter');
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initialize key replacements
|
// Initialize key replacements
|
||||||
let keyReplacements = {};
|
let keyReplacements = {};
|
||||||
|
|
||||||
@ -375,13 +236,14 @@ app.post('/api/stats', async (req, res) => {
|
|||||||
let { username, ssoToken, platform, game, apiCall, sanitize, replaceKeys } =
|
let { username, ssoToken, platform, game, apiCall, sanitize, replaceKeys } =
|
||||||
req.body;
|
req.body;
|
||||||
|
|
||||||
if (!ssoToken && DEFAULT_SSO_TOKEN) {
|
const defaultToken = demoTracker.getDefaultSsoToken();
|
||||||
ssoToken = DEFAULT_SSO_TOKEN;
|
if (!ssoToken && defaultToken) {
|
||||||
|
ssoToken = defaultToken;
|
||||||
logger.info('Using default SSO token for demo mode');
|
logger.info('Using default SSO token for demo mode');
|
||||||
} else if (!ssoToken && !DEFAULT_SSO_TOKEN) {
|
} else if (!ssoToken) {
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
status: 'error',
|
status: 'error',
|
||||||
message: 'SSO Token is required as demo mode is not active',
|
message: 'SSO Token is required',
|
||||||
timestamp: global.Utils.toIsoString(new Date()),
|
timestamp: global.Utils.toIsoString(new Date()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -399,6 +261,11 @@ app.post('/api/stats', async (req, res) => {
|
|||||||
logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`);
|
logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`);
|
||||||
logger.debug("====================="); */
|
logger.debug("====================="); */
|
||||||
|
|
||||||
|
/*
|
||||||
|
if (!ssoToken) {
|
||||||
|
return res.status(400).json({ error: 'SSO Token is required' });
|
||||||
|
} */
|
||||||
|
|
||||||
// For mapList, username is not required
|
// For mapList, username is not required
|
||||||
if (apiCall !== 'mapList' && !username) {
|
if (apiCall !== 'mapList' && !username) {
|
||||||
return res.status(400).json({ error: 'Username is required' });
|
return res.status(400).json({ error: 'Username is required' });
|
||||||
@ -583,7 +450,7 @@ app.post('/api/stats', async (req, res) => {
|
|||||||
|
|
||||||
const { sanitize, replaceKeys } = req.body;
|
const { sanitize, replaceKeys } = req.body;
|
||||||
|
|
||||||
incrementDemoCounter(req, ssoToken);
|
demoTracker.incrementDemoCounter(req, ssoToken);
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
// status: "success",
|
// status: "success",
|
||||||
@ -644,13 +511,20 @@ app.post('/api/matches', async (req, res) => {
|
|||||||
return res.status(400).json({ error: 'Username is required' });
|
return res.status(400).json({ error: 'Username is required' });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ssoToken && DEFAULT_SSO_TOKEN) {
|
if (!username || !ssoToken) {
|
||||||
ssoToken = DEFAULT_SSO_TOKEN;
|
return res
|
||||||
|
.status(400)
|
||||||
|
.json({ error: 'Username and SSO Token are required' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultToken = demoTracker.getDefaultSsoToken();
|
||||||
|
if (!ssoToken && defaultToken) {
|
||||||
|
ssoToken = defaultToken;
|
||||||
logger.info('Using default SSO token for demo mode');
|
logger.info('Using default SSO token for demo mode');
|
||||||
} else if (!ssoToken && !DEFAULT_SSO_TOKEN) {
|
} else if (!ssoToken) {
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
status: 'error',
|
status: 'error',
|
||||||
message: 'SSO Token is required as demo mode is not active',
|
message: 'SSO Token is required',
|
||||||
timestamp: global.Utils.toIsoString(new Date()),
|
timestamp: global.Utils.toIsoString(new Date()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -743,7 +617,7 @@ app.post('/api/matches', async (req, res) => {
|
|||||||
|
|
||||||
const { sanitize, replaceKeys } = req.body;
|
const { sanitize, replaceKeys } = req.body;
|
||||||
|
|
||||||
incrementDemoCounter(req, ssoToken);
|
demoTracker.incrementDemoCounter(req, ssoToken);
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
// status: "success",
|
// status: "success",
|
||||||
@ -800,13 +674,20 @@ app.post('/api/matchInfo', async (req, res) => {
|
|||||||
return res.status(400).json({ error: 'Match ID is required' });
|
return res.status(400).json({ error: 'Match ID is required' });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ssoToken && DEFAULT_SSO_TOKEN) {
|
if (!matchId || !ssoToken) {
|
||||||
ssoToken = DEFAULT_SSO_TOKEN;
|
return res
|
||||||
|
.status(400)
|
||||||
|
.json({ error: 'Match ID and SSO Token are required' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultToken = demoTracker.getDefaultSsoToken();
|
||||||
|
if (!ssoToken && defaultToken) {
|
||||||
|
ssoToken = defaultToken;
|
||||||
logger.info('Using default SSO token for demo mode');
|
logger.info('Using default SSO token for demo mode');
|
||||||
} else if (!ssoToken && !DEFAULT_SSO_TOKEN) {
|
} else if (!ssoToken) {
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
status: 'error',
|
status: 'error',
|
||||||
message: 'SSO Token is required as demo mode is not active',
|
message: 'SSO Token is required',
|
||||||
timestamp: global.Utils.toIsoString(new Date()),
|
timestamp: global.Utils.toIsoString(new Date()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -885,7 +766,7 @@ app.post('/api/matchInfo', async (req, res) => {
|
|||||||
|
|
||||||
const { sanitize, replaceKeys } = req.body;
|
const { sanitize, replaceKeys } = req.body;
|
||||||
|
|
||||||
incrementDemoCounter(req, ssoToken);
|
demoTracker.incrementDemoCounter(req, ssoToken);
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
// status: "success",
|
// status: "success",
|
||||||
@ -939,6 +820,11 @@ app.post('/api/user', async (req, res) => {
|
|||||||
logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`);
|
logger.debug(`Processing Options - Sanitize: ${sanitize}, Replace Keys: ${replaceKeys}`);
|
||||||
logger.debug("========================="); */
|
logger.debug("========================="); */
|
||||||
|
|
||||||
|
/*
|
||||||
|
if (!ssoToken) {
|
||||||
|
return res.status(400).json({ error: 'SSO Token is required' });
|
||||||
|
} */
|
||||||
|
|
||||||
// For eventFeed and identities, username is not required
|
// For eventFeed and identities, username is not required
|
||||||
if (
|
if (
|
||||||
!username &&
|
!username &&
|
||||||
@ -951,13 +837,14 @@ app.post('/api/user', async (req, res) => {
|
|||||||
.json({ error: 'Username is required for this API call' });
|
.json({ error: 'Username is required for this API call' });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ssoToken && DEFAULT_SSO_TOKEN) {
|
const defaultToken = demoTracker.getDefaultSsoToken();
|
||||||
ssoToken = DEFAULT_SSO_TOKEN;
|
if (!ssoToken && defaultToken) {
|
||||||
|
ssoToken = defaultToken;
|
||||||
logger.info('Using default SSO token for demo mode');
|
logger.info('Using default SSO token for demo mode');
|
||||||
} else if (!ssoToken && !DEFAULT_SSO_TOKEN) {
|
} else if (!ssoToken) {
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
status: 'error',
|
status: 'error',
|
||||||
message: 'SSO Token is required as demo mode is not active',
|
message: 'SSO Token is required',
|
||||||
timestamp: global.Utils.toIsoString(new Date()),
|
timestamp: global.Utils.toIsoString(new Date()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1027,7 +914,7 @@ app.post('/api/user', async (req, res) => {
|
|||||||
|
|
||||||
const { sanitize, replaceKeys } = req.body;
|
const { sanitize, replaceKeys } = req.body;
|
||||||
|
|
||||||
incrementDemoCounter(req, ssoToken);
|
demoTracker.incrementDemoCounter(req, ssoToken);
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
// status: "success",
|
// status: "success",
|
||||||
@ -1083,13 +970,20 @@ app.post('/api/search', async (req, res) => {
|
|||||||
return res.status(400).json({ error: 'Username is required' });
|
return res.status(400).json({ error: 'Username is required' });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ssoToken && DEFAULT_SSO_TOKEN) {
|
if (!username || !ssoToken) {
|
||||||
ssoToken = DEFAULT_SSO_TOKEN;
|
return res
|
||||||
|
.status(400)
|
||||||
|
.json({ error: 'Username and SSO Token are required' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultToken = demoTracker.getDefaultSsoToken();
|
||||||
|
if (!ssoToken && defaultToken) {
|
||||||
|
ssoToken = defaultToken;
|
||||||
logger.info('Using default SSO token for demo mode');
|
logger.info('Using default SSO token for demo mode');
|
||||||
} else if (!ssoToken && !DEFAULT_SSO_TOKEN) {
|
} else if (!ssoToken) {
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
status: 'error',
|
status: 'error',
|
||||||
message: 'SSO Token is required as demo mode is not active',
|
message: 'SSO Token is required',
|
||||||
timestamp: global.Utils.toIsoString(new Date()),
|
timestamp: global.Utils.toIsoString(new Date()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1124,7 +1018,7 @@ app.post('/api/search', async (req, res) => {
|
|||||||
|
|
||||||
const { sanitize, replaceKeys } = req.body;
|
const { sanitize, replaceKeys } = req.body;
|
||||||
|
|
||||||
incrementDemoCounter(req, ssoToken);
|
demoTracker.incrementDemoCounter(req, ssoToken);
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
// status: "success",
|
// status: "success",
|
||||||
@ -1228,22 +1122,21 @@ app.get('/health', (req, res) => {
|
|||||||
req.headers['x-forwarded-for']?.split(',')[0] ||
|
req.headers['x-forwarded-for']?.split(',')[0] ||
|
||||||
req.ip ||
|
req.ip ||
|
||||||
req.connection.remoteAddress;
|
req.connection.remoteAddress;
|
||||||
// Check if demo mode is forced via URL parameter
|
|
||||||
const forceDemoMode = req.session && req.session.forceDemoMode === true;
|
|
||||||
|
|
||||||
const isDemoMode = !!DEFAULT_SSO_TOKEN || forceDemoMode;
|
const isDemoMode = demoTracker.isDemoModeActive();
|
||||||
res.json({
|
res.json({
|
||||||
status: 'ok',
|
status: 'ok',
|
||||||
timestamp: global.Utils.toIsoString(new Date()),
|
timestamp: global.Utils.toIsoString(new Date()),
|
||||||
demoMode: isDemoMode,
|
demoMode: isDemoMode,
|
||||||
requestsRemaining:
|
requestsRemaining:
|
||||||
isDemoMode ?
|
isDemoMode ?
|
||||||
demoModeRequestTracker.maxRequestsPerHour -
|
demoTracker.demoModeRequestTracker.maxRequestsPerHour -
|
||||||
(demoModeRequestTracker.requests.get(clientIP)?.count || 0)
|
(demoTracker.demoModeRequestTracker.requests.get(clientIP)?.count || 0)
|
||||||
: null,
|
: null,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
app.get('/demo', (req, res) => {
|
app.get('/demo', (req, res) => {
|
||||||
// Set a cookie or session variable to enable demo mode
|
// Set a cookie or session variable to enable demo mode
|
||||||
if (!req.session) {
|
if (!req.session) {
|
||||||
@ -1253,10 +1146,38 @@ app.get('/demo', (req, res) => {
|
|||||||
|
|
||||||
// Redirect to the main app
|
// Redirect to the main app
|
||||||
res.redirect('/');
|
res.redirect('/');
|
||||||
|
}); */
|
||||||
|
|
||||||
|
// Demo mode route - enables demo mode when visited
|
||||||
|
app.get('/demo', async (req, res) => {
|
||||||
|
const success = await demoTracker.initializeDemoMode(true);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
logger.info('Demo mode activated via /demo route');
|
||||||
|
res.redirect('/?demo=true');
|
||||||
|
} else {
|
||||||
|
res
|
||||||
|
.status(500)
|
||||||
|
.send('Failed to enable demo mode. Check server logs for details.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Demo mode deactivation route
|
||||||
|
app.get('/demo/disable', async (req, res) => {
|
||||||
|
await demoTracker.initializeDemoMode(false);
|
||||||
|
logger.info('Demo mode deactivated via /demo/disable route');
|
||||||
|
res.redirect('/?demo=false');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Serve the main HTML file
|
// Serve the main HTML file
|
||||||
app.get('/', (req, res) => {
|
app.get('/', async (req, res) => {
|
||||||
|
// Check demo mode query parameter
|
||||||
|
if (req.query.demo === 'true') {
|
||||||
|
await demoTracker.initializeDemoMode(true);
|
||||||
|
} else if (req.query.demo === 'false') {
|
||||||
|
await demoTracker.initializeDemoMode(false);
|
||||||
|
}
|
||||||
|
|
||||||
res.sendFile(path.join(__dirname, 'src', 'index.html'));
|
res.sendFile(path.join(__dirname, 'src', 'index.html'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
381
src/js/backend-min.js
vendored
381
src/js/backend-min.js
vendored
@ -1,381 +0,0 @@
|
|||||||
const a0_0x3e5ceb = a0_0x1776;
|
|
||||||
function a0_0x424e() {
|
|
||||||
const _0x308173 = [
|
|
||||||
'8HLbzTb',
|
|
||||||
'number',
|
|
||||||
'replace',
|
|
||||||
'endTime',
|
|
||||||
'timePlayed',
|
|
||||||
'uiAPI',
|
|
||||||
'GMT',
|
|
||||||
'forEach',
|
|
||||||
'[CLIENT]\x20Request\x20data:\x20',
|
|
||||||
'An\x20error\x20occurred',
|
|
||||||
'[CLIENT]\x20Response\x20received\x20at\x20',
|
|
||||||
'\x20Hours\x20',
|
|
||||||
'includes',
|
|
||||||
'\x20at\x20',
|
|
||||||
'getElementById',
|
|
||||||
'dateAdded',
|
|
||||||
'toISOString',
|
|
||||||
'textContent',
|
|
||||||
'Request\x20timed\x20out.\x20Please\x20try\x20again.',
|
|
||||||
'string',
|
|
||||||
'Fetch\x20error:',
|
|
||||||
'length',
|
|
||||||
'828318JvPrOY',
|
|
||||||
'time',
|
|
||||||
'replaceKeysOption',
|
|
||||||
'toString',
|
|
||||||
'substring',
|
|
||||||
'json',
|
|
||||||
'An\x20error\x20occurred\x20while\x20fetching\x20data.',
|
|
||||||
'keys',
|
|
||||||
'entries',
|
|
||||||
'274851lFAePH',
|
|
||||||
'displayError',
|
|
||||||
'toUTCString',
|
|
||||||
'checked',
|
|
||||||
'5mFBRcl',
|
|
||||||
'1809891DxJKoQ',
|
|
||||||
'DOMContentLoaded',
|
|
||||||
'7128aSefdV',
|
|
||||||
'Server\x20returned\x20non-JSON\x20response',
|
|
||||||
'none',
|
|
||||||
'log',
|
|
||||||
'stringify',
|
|
||||||
'currentData',
|
|
||||||
'object',
|
|
||||||
'displayResults',
|
|
||||||
'appState',
|
|
||||||
'headers',
|
|
||||||
'null',
|
|
||||||
'signal',
|
|
||||||
'\x20Days\x20',
|
|
||||||
'error',
|
|
||||||
'style',
|
|
||||||
'test',
|
|
||||||
'\x20Minutes\x20',
|
|
||||||
'utcStartSeconds',
|
|
||||||
'tutorialDismissed',
|
|
||||||
'isArray',
|
|
||||||
'trimStart',
|
|
||||||
'ssoToken',
|
|
||||||
'querySelectorAll',
|
|
||||||
'70rCMOkH',
|
|
||||||
'addEventListener',
|
|
||||||
'1218049tFLRcR',
|
|
||||||
'.tutorial',
|
|
||||||
'repeat',
|
|
||||||
'16176Hmtcab',
|
|
||||||
'message',
|
|
||||||
'loading',
|
|
||||||
'[CLIENT]\x20Response\x20status:\x20',
|
|
||||||
'map',
|
|
||||||
'POST',
|
|
||||||
'floor',
|
|
||||||
'70193EjRYyO',
|
|
||||||
'name',
|
|
||||||
'objTime',
|
|
||||||
'display',
|
|
||||||
'date',
|
|
||||||
'get',
|
|
||||||
'Error:\x20',
|
|
||||||
'content-type',
|
|
||||||
'application/json',
|
|
||||||
'status',
|
|
||||||
'UTC',
|
|
||||||
'boolean',
|
|
||||||
'2031464mgplJK',
|
|
||||||
'8ohsaRV',
|
|
||||||
];
|
|
||||||
a0_0x424e = function () {
|
|
||||||
return _0x308173;
|
|
||||||
};
|
|
||||||
return a0_0x424e();
|
|
||||||
}
|
|
||||||
(function (_0x26b91f, _0x29f7ca) {
|
|
||||||
const _0x4906c0 = a0_0x1776,
|
|
||||||
_0x243b93 = _0x26b91f();
|
|
||||||
while (!![]) {
|
|
||||||
try {
|
|
||||||
const _0x1e80bd =
|
|
||||||
(parseInt(_0x4906c0(0x11e)) / 0x1) *
|
|
||||||
(-parseInt(_0x4906c0(0x12b)) / 0x2) +
|
|
||||||
-parseInt(_0x4906c0(0x150)) / 0x3 +
|
|
||||||
parseInt(_0x4906c0(0x12a)) / 0x4 +
|
|
||||||
(-parseInt(_0x4906c0(0x14f)) / 0x5) *
|
|
||||||
(-parseInt(_0x4906c0(0x142)) / 0x6) +
|
|
||||||
(parseInt(_0x4906c0(0x114)) / 0x7) *
|
|
||||||
(parseInt(_0x4906c0(0x12c)) / 0x8) +
|
|
||||||
(parseInt(_0x4906c0(0x14b)) / 0x9) *
|
|
||||||
(-parseInt(_0x4906c0(0x112)) / 0xa) +
|
|
||||||
(-parseInt(_0x4906c0(0x152)) / 0xb) *
|
|
||||||
(-parseInt(_0x4906c0(0x117)) / 0xc);
|
|
||||||
if (_0x1e80bd === _0x29f7ca) break;
|
|
||||||
else _0x243b93['push'](_0x243b93['shift']());
|
|
||||||
} catch (_0x14bf47) {
|
|
||||||
_0x243b93['push'](_0x243b93['shift']());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})(a0_0x424e, 0x91684),
|
|
||||||
(window['backendAPI'] = {
|
|
||||||
fetchData: fetchData,
|
|
||||||
jsonToYAML: jsonToYAML,
|
|
||||||
formatDuration: formatDuration,
|
|
||||||
formatEpochTime: formatEpochTime,
|
|
||||||
processTimestamps: processTimestamps,
|
|
||||||
}),
|
|
||||||
(window['appState'] = {
|
|
||||||
currentData: null,
|
|
||||||
outputFormat: a0_0x3e5ceb(0x147),
|
|
||||||
tutorialDismissed: ![],
|
|
||||||
}),
|
|
||||||
document[a0_0x3e5ceb(0x113)](a0_0x3e5ceb(0x151), function () {});
|
|
||||||
function jsonToYAML(_0x57e066) {
|
|
||||||
const _0x4e69bf = a0_0x3e5ceb,
|
|
||||||
_0x1e3ebf = 0x2;
|
|
||||||
function _0x58e6eb(_0x4d120a, _0x248fd6 = 0x0) {
|
|
||||||
const _0x3a284a = a0_0x1776,
|
|
||||||
_0x52d5f6 = '\x20'[_0x3a284a(0x116)](_0x248fd6);
|
|
||||||
if (_0x4d120a === null) return _0x3a284a(0x105);
|
|
||||||
if (_0x4d120a === undefined) return '';
|
|
||||||
if (typeof _0x4d120a === _0x3a284a(0x13f)) {
|
|
||||||
if (
|
|
||||||
/[:{}[\],&*#?|\-<>=!%@`]/[_0x3a284a(0x10a)](_0x4d120a) ||
|
|
||||||
_0x4d120a === '' ||
|
|
||||||
!isNaN(_0x4d120a)
|
|
||||||
)
|
|
||||||
return '\x22' + _0x4d120a[_0x3a284a(0x12e)](/"/g, '\x5c\x22') + '\x22';
|
|
||||||
return _0x4d120a;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
typeof _0x4d120a === _0x3a284a(0x12d) ||
|
|
||||||
typeof _0x4d120a === _0x3a284a(0x129)
|
|
||||||
)
|
|
||||||
return _0x4d120a[_0x3a284a(0x145)]();
|
|
||||||
if (Array[_0x3a284a(0x10e)](_0x4d120a)) {
|
|
||||||
if (_0x4d120a[_0x3a284a(0x141)] === 0x0) return '[]';
|
|
||||||
let _0x53132f = '';
|
|
||||||
for (const _0x194c43 of _0x4d120a) {
|
|
||||||
_0x53132f +=
|
|
||||||
'\x0a' +
|
|
||||||
_0x52d5f6 +
|
|
||||||
'-\x20' +
|
|
||||||
_0x58e6eb(_0x194c43, _0x248fd6 + _0x1e3ebf)[_0x3a284a(0x10f)]();
|
|
||||||
}
|
|
||||||
return _0x53132f;
|
|
||||||
}
|
|
||||||
if (typeof _0x4d120a === 'object') {
|
|
||||||
if (Object[_0x3a284a(0x149)](_0x4d120a)[_0x3a284a(0x141)] === 0x0)
|
|
||||||
return '{}';
|
|
||||||
let _0x42a784 = '';
|
|
||||||
for (const [_0x1a8665, _0x32613d] of Object[_0x3a284a(0x14a)](
|
|
||||||
_0x4d120a
|
|
||||||
)) {
|
|
||||||
const _0x3a78cc = _0x58e6eb(_0x32613d, _0x248fd6 + _0x1e3ebf);
|
|
||||||
_0x3a78cc[_0x3a284a(0x138)]('\x0a') ?
|
|
||||||
(_0x42a784 += '\x0a' + _0x52d5f6 + _0x1a8665 + ':' + _0x3a78cc)
|
|
||||||
: (_0x42a784 += '\x0a' + _0x52d5f6 + _0x1a8665 + ':\x20' + _0x3a78cc);
|
|
||||||
}
|
|
||||||
return _0x42a784;
|
|
||||||
}
|
|
||||||
return String(_0x4d120a);
|
|
||||||
}
|
|
||||||
return _0x58e6eb(_0x57e066, 0x0)[_0x4e69bf(0x146)](0x1);
|
|
||||||
}
|
|
||||||
async function fetchData(_0x5d7e74, _0x3de528) {
|
|
||||||
const _0x5f0540 = a0_0x3e5ceb;
|
|
||||||
console['log'](
|
|
||||||
'[CLIENT]\x20Request\x20to\x20' +
|
|
||||||
_0x5d7e74 +
|
|
||||||
_0x5f0540(0x139) +
|
|
||||||
new Date()[_0x5f0540(0x13c)]()
|
|
||||||
),
|
|
||||||
console['log'](
|
|
||||||
_0x5f0540(0x134) +
|
|
||||||
JSON[_0x5f0540(0x156)]({
|
|
||||||
..._0x3de528,
|
|
||||||
ssoToken:
|
|
||||||
_0x3de528[_0x5f0540(0x110)] ?
|
|
||||||
_0x3de528['ssoToken'][_0x5f0540(0x146)](0x0, 0x5) + '...'
|
|
||||||
: _0x5f0540(0x154),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
const _0x4d0c9e = document['getElementById']('error'),
|
|
||||||
_0x4a22b8 = document['getElementById'](_0x5f0540(0x119)),
|
|
||||||
_0x2f0994 = document[_0x5f0540(0x13a)]('results');
|
|
||||||
(_0x4d0c9e[_0x5f0540(0x13d)] = ''),
|
|
||||||
(_0x2f0994[_0x5f0540(0x109)][_0x5f0540(0x121)] = _0x5f0540(0x154)),
|
|
||||||
(_0x4a22b8[_0x5f0540(0x109)][_0x5f0540(0x121)] = 'block');
|
|
||||||
!window[_0x5f0540(0x103)][_0x5f0540(0x10d)] &&
|
|
||||||
((window[_0x5f0540(0x103)][_0x5f0540(0x10d)] = !![]),
|
|
||||||
document[_0x5f0540(0x111)](_0x5f0540(0x115))[_0x5f0540(0x133)](
|
|
||||||
(_0x520561) => {
|
|
||||||
const _0x511e86 = _0x5f0540;
|
|
||||||
_0x520561[_0x511e86(0x109)][_0x511e86(0x121)] = _0x511e86(0x154);
|
|
||||||
}
|
|
||||||
));
|
|
||||||
if (!_0x3de528[_0x5f0540(0x110)]) {
|
|
||||||
window[_0x5f0540(0x131)][_0x5f0540(0x14c)](
|
|
||||||
'SSO\x20Token\x20is\x20required'
|
|
||||||
),
|
|
||||||
(_0x4a22b8[_0x5f0540(0x109)][_0x5f0540(0x121)] = _0x5f0540(0x154));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const _0x4c0dfe = new AbortController(),
|
|
||||||
_0x5e0646 = setTimeout(() => _0x4c0dfe['abort'](), 0x7530),
|
|
||||||
_0x7ef5a8 = await fetch(_0x5d7e74, {
|
|
||||||
method: _0x5f0540(0x11c),
|
|
||||||
headers: { 'Content-Type': _0x5f0540(0x126) },
|
|
||||||
body: JSON[_0x5f0540(0x156)](_0x3de528),
|
|
||||||
signal: _0x4c0dfe[_0x5f0540(0x106)],
|
|
||||||
});
|
|
||||||
clearTimeout(_0x5e0646);
|
|
||||||
const _0x64ad7 = _0x7ef5a8[_0x5f0540(0x104)][_0x5f0540(0x123)](
|
|
||||||
_0x5f0540(0x125)
|
|
||||||
);
|
|
||||||
if (!_0x64ad7 || !_0x64ad7[_0x5f0540(0x138)](_0x5f0540(0x126)))
|
|
||||||
throw new Error(_0x5f0540(0x153));
|
|
||||||
const _0x572a77 = await _0x7ef5a8[_0x5f0540(0x147)]();
|
|
||||||
console['log'](_0x5f0540(0x136) + new Date()['toISOString']()),
|
|
||||||
console[_0x5f0540(0x155)](_0x5f0540(0x11a) + _0x7ef5a8['status']),
|
|
||||||
console[_0x5f0540(0x155)](
|
|
||||||
'[CLIENT]\x20Response\x20size:\x20~' +
|
|
||||||
JSON[_0x5f0540(0x156)](_0x572a77)[_0x5f0540(0x141)] / 0x400 +
|
|
||||||
'\x20KB'
|
|
||||||
);
|
|
||||||
if (!_0x7ef5a8['ok'])
|
|
||||||
throw new Error(
|
|
||||||
_0x572a77[_0x5f0540(0x108)] ||
|
|
||||||
_0x572a77[_0x5f0540(0x118)] ||
|
|
||||||
_0x5f0540(0x124) + _0x7ef5a8[_0x5f0540(0x127)]
|
|
||||||
);
|
|
||||||
if (_0x572a77['error'])
|
|
||||||
window[_0x5f0540(0x131)][_0x5f0540(0x14c)](_0x572a77[_0x5f0540(0x108)]);
|
|
||||||
else
|
|
||||||
_0x572a77['status'] === _0x5f0540(0x108) ?
|
|
||||||
window[_0x5f0540(0x131)][_0x5f0540(0x14c)](
|
|
||||||
_0x572a77['message'] || _0x5f0540(0x135)
|
|
||||||
)
|
|
||||||
: ((window[_0x5f0540(0x103)][_0x5f0540(0x100)] = _0x572a77),
|
|
||||||
window[_0x5f0540(0x131)][_0x5f0540(0x102)](_0x572a77));
|
|
||||||
} catch (_0x563951) {
|
|
||||||
_0x563951[_0x5f0540(0x11f)] === 'AbortError' ?
|
|
||||||
window[_0x5f0540(0x131)]['displayError'](_0x5f0540(0x13e))
|
|
||||||
: (window['uiAPI'][_0x5f0540(0x14c)](
|
|
||||||
_0x5f0540(0x124) + (_0x563951[_0x5f0540(0x118)] || _0x5f0540(0x148))
|
|
||||||
),
|
|
||||||
console['error'](_0x5f0540(0x140), _0x563951));
|
|
||||||
} finally {
|
|
||||||
_0x4a22b8[_0x5f0540(0x109)][_0x5f0540(0x121)] = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function formatDuration(_0x58e5da) {
|
|
||||||
const _0x547bb4 = a0_0x3e5ceb;
|
|
||||||
if (!_0x58e5da || isNaN(_0x58e5da)) return _0x58e5da;
|
|
||||||
const _0x2efa31 = parseFloat(_0x58e5da),
|
|
||||||
_0x5a8e77 = Math[_0x547bb4(0x11d)](_0x2efa31 / 0x15180),
|
|
||||||
_0x31860f = Math[_0x547bb4(0x11d)]((_0x2efa31 % 0x15180) / 0xe10),
|
|
||||||
_0x40f2d9 = Math[_0x547bb4(0x11d)]((_0x2efa31 % 0xe10) / 0x3c),
|
|
||||||
_0x364d47 = Math[_0x547bb4(0x11d)](_0x2efa31 % 0x3c);
|
|
||||||
return (
|
|
||||||
_0x5a8e77 +
|
|
||||||
_0x547bb4(0x107) +
|
|
||||||
_0x31860f +
|
|
||||||
_0x547bb4(0x137) +
|
|
||||||
_0x40f2d9 +
|
|
||||||
_0x547bb4(0x10b) +
|
|
||||||
_0x364d47 +
|
|
||||||
'\x20Seconds'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
function formatEpochTime(_0x2ad881, _0x376584) {
|
|
||||||
const _0x564df4 = a0_0x3e5ceb;
|
|
||||||
if (!_0x2ad881) return _0x2ad881;
|
|
||||||
const _0x2eb117 = parseInt(_0x2ad881);
|
|
||||||
if (isNaN(_0x2eb117)) return _0x2ad881;
|
|
||||||
const _0x403f03 =
|
|
||||||
_0x2eb117['toString']()['length'] <= 0xa ? _0x2eb117 * 0x3e8 : _0x2eb117;
|
|
||||||
let _0x56476d = 0x0;
|
|
||||||
if (_0x376584 !== _0x564df4(0x128)) {
|
|
||||||
const _0x34e2a2 = _0x376584['match'](/GMT([+-])(\d+)(?::(\d+))?/);
|
|
||||||
if (_0x34e2a2) {
|
|
||||||
const _0x27099d = _0x34e2a2[0x1] === '+' ? 0x1 : -0x1,
|
|
||||||
_0x16eec3 = parseInt(_0x34e2a2[0x2]),
|
|
||||||
_0x3bff6c = _0x34e2a2[0x3] ? parseInt(_0x34e2a2[0x3]) : 0x0;
|
|
||||||
_0x56476d = _0x27099d * (_0x16eec3 * 0x3c + _0x3bff6c) * 0x3c * 0x3e8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const _0x56f46a = new Date(_0x403f03 + _0x56476d);
|
|
||||||
return _0x56f46a[_0x564df4(0x14d)]()[_0x564df4(0x12e)](
|
|
||||||
_0x564df4(0x132),
|
|
||||||
_0x376584
|
|
||||||
);
|
|
||||||
}
|
|
||||||
function a0_0x1776(_0x51f135, _0x4e9ae9) {
|
|
||||||
const _0x424e92 = a0_0x424e();
|
|
||||||
return (
|
|
||||||
(a0_0x1776 = function (_0x177606, _0x203857) {
|
|
||||||
_0x177606 = _0x177606 - 0x100;
|
|
||||||
let _0x3fd5d4 = _0x424e92[_0x177606];
|
|
||||||
return _0x3fd5d4;
|
|
||||||
}),
|
|
||||||
a0_0x1776(_0x51f135, _0x4e9ae9)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
function processTimestamps(
|
|
||||||
_0x13d227,
|
|
||||||
_0x1f087c,
|
|
||||||
_0x345fbc = [
|
|
||||||
a0_0x3e5ceb(0x122),
|
|
||||||
a0_0x3e5ceb(0x13b),
|
|
||||||
a0_0x3e5ceb(0x10c),
|
|
||||||
'utcEndSeconds',
|
|
||||||
'timestamp',
|
|
||||||
'startTime',
|
|
||||||
a0_0x3e5ceb(0x12f),
|
|
||||||
],
|
|
||||||
_0x3c9b02 = [
|
|
||||||
a0_0x3e5ceb(0x143),
|
|
||||||
'timePlayedTotal',
|
|
||||||
a0_0x3e5ceb(0x130),
|
|
||||||
'avgLifeTime',
|
|
||||||
'duration',
|
|
||||||
a0_0x3e5ceb(0x120),
|
|
||||||
]
|
|
||||||
) {
|
|
||||||
const _0x4c2013 = a0_0x3e5ceb;
|
|
||||||
if (!_0x13d227 || typeof _0x13d227 !== _0x4c2013(0x101)) return _0x13d227;
|
|
||||||
if (Array[_0x4c2013(0x10e)](_0x13d227))
|
|
||||||
return _0x13d227[_0x4c2013(0x11b)]((_0x2088ab) =>
|
|
||||||
processTimestamps(_0x2088ab, _0x1f087c, _0x345fbc, _0x3c9b02)
|
|
||||||
);
|
|
||||||
const _0x134b47 = {};
|
|
||||||
for (const [_0x1a08ca, _0x13b85e] of Object[_0x4c2013(0x14a)](_0x13d227)) {
|
|
||||||
if (
|
|
||||||
_0x345fbc[_0x4c2013(0x138)](_0x1a08ca) &&
|
|
||||||
typeof _0x13b85e === _0x4c2013(0x12d)
|
|
||||||
)
|
|
||||||
_0x134b47[_0x1a08ca] = formatEpochTime(_0x13b85e, _0x1f087c);
|
|
||||||
else {
|
|
||||||
if (
|
|
||||||
_0x3c9b02[_0x4c2013(0x138)](_0x1a08ca) &&
|
|
||||||
typeof _0x13b85e === 'number' &&
|
|
||||||
document[_0x4c2013(0x13a)](_0x4c2013(0x144))[_0x4c2013(0x14e)]
|
|
||||||
)
|
|
||||||
_0x134b47[_0x1a08ca] = formatDuration(_0x13b85e);
|
|
||||||
else
|
|
||||||
typeof _0x13b85e === _0x4c2013(0x101) && _0x13b85e !== null ?
|
|
||||||
(_0x134b47[_0x1a08ca] = processTimestamps(
|
|
||||||
_0x13b85e,
|
|
||||||
_0x1f087c,
|
|
||||||
_0x345fbc,
|
|
||||||
_0x3c9b02
|
|
||||||
))
|
|
||||||
: (_0x134b47[_0x1a08ca] = _0x13b85e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return _0x134b47;
|
|
||||||
}
|
|
209
src/js/demoTracker.js
Normal file
209
src/js/demoTracker.js
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
/**
|
||||||
|
* Demo Mode Tracker Module
|
||||||
|
* Handles tracking and limiting demo mode API requests
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { logger } = require('./logger.js');
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
// Demo mode state
|
||||||
|
let demoModeActive = false;
|
||||||
|
let defaultSsoToken = '';
|
||||||
|
|
||||||
|
// Configure request tracking for demo mode
|
||||||
|
const demoModeRequestTracker = {
|
||||||
|
requests: new Map(),
|
||||||
|
maxRequestsPerHour: 20, // Adjust as needed
|
||||||
|
resetInterval: 60 * 60 * 1000, // 1 hour in milliseconds
|
||||||
|
|
||||||
|
isLimited: function (ip) {
|
||||||
|
const now = Date.now();
|
||||||
|
const userRequests = this.requests.get(ip) || {
|
||||||
|
count: 0,
|
||||||
|
resetTime: now + this.resetInterval,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reset counter if the reset time has passed
|
||||||
|
if (now > userRequests.resetTime) {
|
||||||
|
userRequests.count = 0;
|
||||||
|
userRequests.resetTime = now + this.resetInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
return userRequests.count >= this.maxRequestsPerHour;
|
||||||
|
},
|
||||||
|
|
||||||
|
incrementCounter: function (ip) {
|
||||||
|
const now = Date.now();
|
||||||
|
const userRequests = this.requests.get(ip) || {
|
||||||
|
count: 0,
|
||||||
|
resetTime: now + this.resetInterval,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reset counter if the reset time has passed
|
||||||
|
if (now > userRequests.resetTime) {
|
||||||
|
userRequests.count = 1; // Start with 1 for this request
|
||||||
|
userRequests.resetTime = now + this.resetInterval;
|
||||||
|
} else {
|
||||||
|
userRequests.count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.requests.set(ip, userRequests);
|
||||||
|
return userRequests.count;
|
||||||
|
},
|
||||||
|
|
||||||
|
cleanupExpiredEntries: function () {
|
||||||
|
const now = Date.now();
|
||||||
|
for (const [ip, data] of this.requests.entries()) {
|
||||||
|
if (now > data.resetTime) {
|
||||||
|
// Remove expired entries
|
||||||
|
this.requests.delete(ip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getRemainingRequests: function (ip) {
|
||||||
|
if (!demoModeActive) return null;
|
||||||
|
|
||||||
|
const userRequests = this.requests.get(ip) || { count: 0 };
|
||||||
|
return this.maxRequestsPerHour - userRequests.count;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize demo mode
|
||||||
|
* @param {boolean} active Whether to activate demo mode
|
||||||
|
* @returns {Promise<boolean>} Whether demo mode was activated successfully
|
||||||
|
*/
|
||||||
|
async function initializeDemoMode(active) {
|
||||||
|
// If active is explicitly false, disable demo mode
|
||||||
|
if (active === false) {
|
||||||
|
demoModeActive = false;
|
||||||
|
defaultSsoToken = '';
|
||||||
|
logger.info('Demo mode explicitly disabled');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If active is true but demo mode is already active, just return true
|
||||||
|
if (active === true && demoModeActive && defaultSsoToken) {
|
||||||
|
logger.debug('Demo mode already active');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to read the token from a file if active is true
|
||||||
|
if (active === true) {
|
||||||
|
try {
|
||||||
|
const tokenPath = path.join(__dirname, '..', '..', 'token.txt');
|
||||||
|
if (fs.existsSync(tokenPath)) {
|
||||||
|
defaultSsoToken = fs.readFileSync(tokenPath, 'utf8').trim();
|
||||||
|
if (defaultSsoToken) {
|
||||||
|
demoModeActive = true;
|
||||||
|
logger.info('Default SSO token loaded successfully for demo mode');
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
logger.warn('token.txt exists but contains no token');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.warn('token.txt not found, demo mode cannot be activated');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error loading token file:', { error: error.message });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If active is not explicitly set, return current state
|
||||||
|
return demoModeActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demo mode middleware to check request limits
|
||||||
|
*/
|
||||||
|
function demoModeMiddleware(req, res, next) {
|
||||||
|
// Skip if demo mode is not active
|
||||||
|
if (!demoModeActive) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip non-API routes
|
||||||
|
if (!req.path.startsWith('/api/')) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get client IP for tracking
|
||||||
|
const clientIP =
|
||||||
|
req.headers['cf-connecting-ip'] ||
|
||||||
|
req.headers['x-forwarded-for']?.split(',')[0] ||
|
||||||
|
req.ip ||
|
||||||
|
req.connection.remoteAddress;
|
||||||
|
|
||||||
|
// Check for rate limit
|
||||||
|
if (demoModeRequestTracker.isLimited(clientIP)) {
|
||||||
|
const userRequests = demoModeRequestTracker.requests.get(clientIP);
|
||||||
|
const resetTimeMs = userRequests.resetTime - Date.now();
|
||||||
|
|
||||||
|
// Format time as HH:MM:SS
|
||||||
|
const hours = Math.floor(resetTimeMs / (60 * 60 * 1000));
|
||||||
|
const minutes = Math.floor((resetTimeMs % (60 * 60 * 1000)) / (60 * 1000));
|
||||||
|
const seconds = Math.floor((resetTimeMs % (60 * 1000)) / 1000);
|
||||||
|
const timeFormat = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
||||||
|
|
||||||
|
logger.warn(`Demo mode rate limit exceeded for IP: ${clientIP}`);
|
||||||
|
return res.status(429).json({
|
||||||
|
status: 'error',
|
||||||
|
message: `Please try again in ${timeFormat} or use your own SSO token.`,
|
||||||
|
timestamp: global.Utils.toIsoString(new Date()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increment the demo request counter when a valid API call is made
|
||||||
|
* @param {Object} req Express request object
|
||||||
|
* @param {string} ssoToken The token used for the request
|
||||||
|
* @returns {number|null} The new count or null if not in demo mode
|
||||||
|
*/
|
||||||
|
function incrementDemoCounter(req, ssoToken) {
|
||||||
|
if (!demoModeActive || ssoToken !== defaultSsoToken) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const clientIP =
|
||||||
|
req.headers['cf-connecting-ip'] ||
|
||||||
|
req.headers['x-forwarded-for']?.split(',')[0] ||
|
||||||
|
req.ip ||
|
||||||
|
req.connection.remoteAddress;
|
||||||
|
|
||||||
|
const count = demoModeRequestTracker.incrementCounter(clientIP);
|
||||||
|
logger.debug(`Demo mode request count for ${clientIP}: ${count}`);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the default SSO token if demo mode is active
|
||||||
|
* @returns {string|null} The default SSO token or null if demo mode is not active
|
||||||
|
*/
|
||||||
|
function getDefaultSsoToken() {
|
||||||
|
return demoModeActive ? defaultSsoToken : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if demo mode is active
|
||||||
|
* @returns {boolean} Whether demo mode is active
|
||||||
|
*/
|
||||||
|
function isDemoModeActive() {
|
||||||
|
return demoModeActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
initializeDemoMode,
|
||||||
|
demoModeMiddleware,
|
||||||
|
incrementDemoCounter,
|
||||||
|
getDefaultSsoToken,
|
||||||
|
isDemoModeActive,
|
||||||
|
demoModeRequestTracker,
|
||||||
|
};
|
@ -57,6 +57,8 @@ const clientLogger = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
window.addEventListener('popstate', checkDemoMode);
|
||||||
|
|
||||||
// Initialize once DOM is loaded
|
// Initialize once DOM is loaded
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
initTabSwitching();
|
initTabSwitching();
|
||||||
@ -86,9 +88,18 @@ function checkDemoMode() {
|
|||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
console.log('Demo mode status:', data.demoMode);
|
console.log('Demo mode status:', data.demoMode);
|
||||||
if (data.demoMode) {
|
// Check for URL parameter as well
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const demoParam = urlParams.get('demo');
|
||||||
|
|
||||||
|
if (data.demoMode || demoParam === 'true') {
|
||||||
enableDemoMode();
|
enableDemoMode();
|
||||||
|
} else if (demoParam === 'false' || !data.demoMode) {
|
||||||
|
disableDemoMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update request limit information
|
||||||
|
updateRequestLimitInfo();
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Error checking demo mode:', error);
|
console.error('Error checking demo mode:', error);
|
||||||
@ -128,6 +139,23 @@ function enableDemoMode() {
|
|||||||
window.appState.demoMode = true;
|
window.appState.demoMode = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function disableDemoMode() {
|
||||||
|
const ssoTokenInput = document.getElementById('ssoToken');
|
||||||
|
if (ssoTokenInput) {
|
||||||
|
ssoTokenInput.disabled = false;
|
||||||
|
ssoTokenInput.placeholder = 'Enter your SSO token';
|
||||||
|
ssoTokenInput.classList.remove('demo-mode');
|
||||||
|
}
|
||||||
|
|
||||||
|
const demoNotice = document.querySelector('.demo-notice');
|
||||||
|
if (demoNotice) {
|
||||||
|
demoNotice.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.appState = window.appState || {};
|
||||||
|
window.appState.demoMode = false;
|
||||||
|
}
|
||||||
|
|
||||||
function updateRequestLimitInfo() {
|
function updateRequestLimitInfo() {
|
||||||
fetch('/health')
|
fetch('/health')
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
|
Loading…
x
Reference in New Issue
Block a user