snapshot
This commit is contained in:
@@ -278,28 +278,27 @@
|
|||||||
const STATUS_WS_URL = 'wss://ntfy.sh/STATUS_klubhaus_topic/ws';
|
const STATUS_WS_URL = 'wss://ntfy.sh/STATUS_klubhaus_topic/ws';
|
||||||
|
|
||||||
// ============== BACKOFF CONFIGURATION ==============
|
// ============== BACKOFF CONFIGURATION ==============
|
||||||
const ACTIVE_DURATION = 180000; // 3 minutes full speed after interaction
|
const ACTIVE_DURATION = 180000;
|
||||||
const BASE_INTERVAL = 5000; // 5s when active
|
const BASE_INTERVAL = 5000;
|
||||||
const BACKOFF_INTERVALS = [ // Progressive backoff after idle
|
const BACKOFF_INTERVALS = [
|
||||||
10000, // 0-5 min idle: 10s
|
10000,
|
||||||
30000, // 5-10 min idle: 30s
|
30000,
|
||||||
60000, // 10-20 min idle: 60s
|
60000,
|
||||||
120000, // 20-40 min idle: 2min
|
120000,
|
||||||
300000 // 40+ min idle: 5min
|
300000
|
||||||
];
|
];
|
||||||
|
|
||||||
// ============== CLIENT ID (persistent, anonymous) ==============
|
// ============== CLIENT ID (persistent, anonymous) ==============
|
||||||
function getOrCreateClientId() {
|
function getOrCreateClientId() {
|
||||||
let id = localStorage.getItem('klubhaus_client_id');
|
let id = localStorage.getItem('klubhaus_client_id');
|
||||||
if (!id) {
|
if (!id) {
|
||||||
// Fingerprint from stable browser characteristics (no PII)
|
|
||||||
const seed = [
|
const seed = [
|
||||||
navigator.userAgent.split('/')[0], // Just "Mozilla" or "Chrome"
|
navigator.userAgent.split('/')[0],
|
||||||
screen.width,
|
screen.width,
|
||||||
screen.height,
|
screen.height,
|
||||||
navigator.hardwareConcurrency || 'unknown',
|
navigator.hardwareConcurrency || 'unknown',
|
||||||
new Date().getTimezoneOffset(),
|
new Date().getTimezoneOffset(),
|
||||||
Math.random().toString(36).substring(2, 8) // 6 random chars
|
Math.random().toString(36).substring(2, 8)
|
||||||
].join('|');
|
].join('|');
|
||||||
|
|
||||||
id = 'web-' + hashString(seed).substring(0, 12);
|
id = 'web-' + hashString(seed).substring(0, 12);
|
||||||
@@ -325,7 +324,6 @@
|
|||||||
if (saved) {
|
if (saved) {
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(saved);
|
const parsed = JSON.parse(saved);
|
||||||
// Restore with defaults for missing fields
|
|
||||||
return {
|
return {
|
||||||
totalPosted: parsed.totalPosted || 0,
|
totalPosted: parsed.totalPosted || 0,
|
||||||
totalConfirmed: parsed.totalConfirmed || 0,
|
totalConfirmed: parsed.totalConfirmed || 0,
|
||||||
@@ -352,7 +350,6 @@
|
|||||||
localStorage.setItem('klubhaus_metrics', JSON.stringify(metrics));
|
localStorage.setItem('klubhaus_metrics', JSON.stringify(metrics));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize metrics from storage
|
|
||||||
const metrics = loadMetrics();
|
const metrics = loadMetrics();
|
||||||
|
|
||||||
// ============== BACKOFF STATE ==============
|
// ============== BACKOFF STATE ==============
|
||||||
@@ -374,16 +371,16 @@
|
|||||||
const idleTime = Date.now() - lastInteraction;
|
const idleTime = Date.now() - lastInteraction;
|
||||||
|
|
||||||
if (idleTime < ACTIVE_DURATION) {
|
if (idleTime < ACTIVE_DURATION) {
|
||||||
return BASE_INTERVAL; // Full speed for 3 min
|
return BASE_INTERVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
const idleMinutes = (idleTime - ACTIVE_DURATION) / 60000;
|
const idleMinutes = (idleTime - ACTIVE_DURATION) / 60000;
|
||||||
|
|
||||||
if (idleMinutes < 5) return BACKOFF_INTERVALS[0]; // 10s
|
if (idleMinutes < 5) return BACKOFF_INTERVALS[0];
|
||||||
if (idleMinutes < 10) return BACKOFF_INTERVALS[1]; // 30s
|
if (idleMinutes < 10) return BACKOFF_INTERVALS[1];
|
||||||
if (idleMinutes < 20) return BACKOFF_INTERVALS[2]; // 60s
|
if (idleMinutes < 20) return BACKOFF_INTERVALS[2];
|
||||||
if (idleMinutes < 40) return BACKOFF_INTERVALS[3]; // 2min
|
if (idleMinutes < 40) return BACKOFF_INTERVALS[3];
|
||||||
return BACKOFF_INTERVALS[4]; // 5min
|
return BACKOFF_INTERVALS[4];
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateBackoffDisplay() {
|
function updateBackoffDisplay() {
|
||||||
@@ -402,7 +399,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update display every 10 seconds
|
|
||||||
setInterval(updateBackoffDisplay, 10000);
|
setInterval(updateBackoffDisplay, 10000);
|
||||||
|
|
||||||
// ============== WEBSOCKET WITH ADAPTIVE RECONNECT ==============
|
// ============== WEBSOCKET WITH ADAPTIVE RECONNECT ==============
|
||||||
@@ -441,8 +437,6 @@
|
|||||||
const nextInterval = getCurrentInterval();
|
const nextInterval = getCurrentInterval();
|
||||||
connEl.textContent = `WebSocket: reconnect in ${nextInterval/1000}s...`;
|
connEl.textContent = `WebSocket: reconnect in ${nextInterval/1000}s...`;
|
||||||
console.log(`WebSocket closed, reconnecting in ${nextInterval}ms...`);
|
console.log(`WebSocket closed, reconnecting in ${nextInterval}ms...`);
|
||||||
|
|
||||||
// Adaptive reconnect based on idle time
|
|
||||||
setTimeout(connectWebSocket, nextInterval);
|
setTimeout(connectWebSocket, nextInterval);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -464,7 +458,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
metrics.totalReceived++;
|
metrics.totalReceived++;
|
||||||
saveMetrics(); // Persist immediately
|
saveMetrics();
|
||||||
|
|
||||||
updateStatusDisplay(payload);
|
updateStatusDisplay(payload);
|
||||||
updateMetricsDisplay();
|
updateMetricsDisplay();
|
||||||
@@ -501,7 +495,7 @@
|
|||||||
|
|
||||||
// ============== ALERT SENDING ==============
|
// ============== ALERT SENDING ==============
|
||||||
async function sendAlert(message) {
|
async function sendAlert(message) {
|
||||||
recordInteraction(); // Reset backoff
|
recordInteraction();
|
||||||
|
|
||||||
if (isProcessing) return;
|
if (isProcessing) return;
|
||||||
isProcessing = true;
|
isProcessing = true;
|
||||||
@@ -558,7 +552,7 @@
|
|||||||
const check = setInterval(() => {
|
const check = setInterval(() => {
|
||||||
if (!isProcessing || Date.now() > deadline) {
|
if (!isProcessing || Date.now() > deadline) {
|
||||||
clearInterval(check);
|
clearInterval(check);
|
||||||
resolve(!isProcessing); // Resolved if finishConfirmation called
|
resolve(!isProcessing);
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
});
|
});
|
||||||
@@ -569,7 +563,6 @@
|
|||||||
|
|
||||||
while (Date.now() < deadline) {
|
while (Date.now() < deadline) {
|
||||||
try {
|
try {
|
||||||
// Short poll without WebSocket
|
|
||||||
const response = await fetch('https://ntfy.sh/STATUS_klubhaus_topic/json', {
|
const response = await fetch('https://ntfy.sh/STATUS_klubhaus_topic/json', {
|
||||||
signal: AbortSignal.timeout(3000)
|
signal: AbortSignal.timeout(3000)
|
||||||
});
|
});
|
||||||
@@ -716,9 +709,30 @@
|
|||||||
el.textContent = `${CLIENT_ID} | posted:${metrics.totalPosted} confirmed:${metrics.totalConfirmed} errors:${metrics.totalErrors} avg:${avgRtt}ms | session:${sessionHours}h`;
|
el.textContent = `${CLIENT_ID} | posted:${metrics.totalPosted} confirmed:${metrics.totalConfirmed} errors:${metrics.totalErrors} avg:${avgRtt}ms | session:${sessionHours}h`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============== AGGREGATE METRICS (with backoff-aware interval) ==============
|
// ============== AGGREGATE METRICS (deduplicated) ==============
|
||||||
|
|
||||||
|
let lastMetricsHash = null;
|
||||||
|
let lastMetricsTimestamp = 0;
|
||||||
|
|
||||||
|
function hashMetricsPayload(payload) {
|
||||||
|
const canonical = JSON.stringify({
|
||||||
|
client: payload.client,
|
||||||
|
totalPosted: payload.metrics.totalPosted,
|
||||||
|
totalConfirmed: payload.metrics.totalConfirmed,
|
||||||
|
totalReceived: payload.metrics.totalReceived,
|
||||||
|
totalErrors: payload.metrics.totalErrors,
|
||||||
|
avgRoundTripMs: payload.metrics.avgRoundTripMs
|
||||||
|
});
|
||||||
|
|
||||||
|
let hash = 0x811c9dc5;
|
||||||
|
for (let i = 0; i < canonical.length; i++) {
|
||||||
|
hash ^= canonical.charCodeAt(i);
|
||||||
|
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
|
||||||
|
}
|
||||||
|
return (hash >>> 0).toString(16);
|
||||||
|
}
|
||||||
|
|
||||||
function scheduleMetricsPublish() {
|
function scheduleMetricsPublish() {
|
||||||
// Clear existing
|
|
||||||
if (metricsIntervalId) clearInterval(metricsIntervalId);
|
if (metricsIntervalId) clearInterval(metricsIntervalId);
|
||||||
|
|
||||||
const interval = getCurrentInterval();
|
const interval = getCurrentInterval();
|
||||||
@@ -740,18 +754,28 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
const currentHash = hashMetricsPayload(payload);
|
||||||
await fetch(METRICS_POST_URL, {
|
const now = Date.now();
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify(payload),
|
if (currentHash === lastMetricsHash && (now - lastMetricsTimestamp) < 300000) {
|
||||||
headers: { 'Content-Type': 'application/json' }
|
console.log('Metrics unchanged, skipping emission');
|
||||||
});
|
} else {
|
||||||
console.log('Aggregate metrics published');
|
try {
|
||||||
} catch (e) {
|
await fetch(METRICS_POST_URL, {
|
||||||
console.error('Metrics publish failed:', e.message);
|
method: 'POST',
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
|
console.log('Aggregate metrics published');
|
||||||
|
|
||||||
|
lastMetricsHash = currentHash;
|
||||||
|
lastMetricsTimestamp = now;
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Metrics publish failed:', e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reschedule with potentially new interval
|
|
||||||
scheduleMetricsPublish();
|
scheduleMetricsPublish();
|
||||||
|
|
||||||
}, interval);
|
}, interval);
|
||||||
@@ -762,7 +786,6 @@
|
|||||||
if (e.key === 'Enter') sendCustom();
|
if (e.key === 'Enter') sendCustom();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Track any interaction
|
|
||||||
document.addEventListener('click', recordInteraction);
|
document.addEventListener('click', recordInteraction);
|
||||||
document.addEventListener('touchstart', recordInteraction);
|
document.addEventListener('touchstart', recordInteraction);
|
||||||
document.addEventListener('keydown', recordInteraction);
|
document.addEventListener('keydown', recordInteraction);
|
||||||
@@ -772,7 +795,7 @@
|
|||||||
updateMetricsDisplay();
|
updateMetricsDisplay();
|
||||||
updateBackoffDisplay();
|
updateBackoffDisplay();
|
||||||
|
|
||||||
console.log('KLUBHAUS ALERT v4.1 initialized');
|
console.log('KLUBHAUS ALERT v4.2 initialized');
|
||||||
console.log('Client ID:', CLIENT_ID);
|
console.log('Client ID:', CLIENT_ID);
|
||||||
console.log('Loaded metrics:', metrics);
|
console.log('Loaded metrics:', metrics);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user