feat: simplify pollSessions to use federation proxy endpoint
This commit is contained in:
+8
-61
@@ -303,10 +303,11 @@ function setConnectionStatus(level) {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async function pollSessions() {
|
async function pollSessions() {
|
||||||
// Falls back to local-only if _sources is empty
|
|
||||||
if (_sources.length === 0) {
|
|
||||||
try {
|
try {
|
||||||
const res = await api('GET', '/api/sessions');
|
var endpoint = (_serverSettings && _serverSettings.multi_device_enabled)
|
||||||
|
? '/api/federation/sessions'
|
||||||
|
: '/api/sessions';
|
||||||
|
const res = await api('GET', endpoint);
|
||||||
const sessions = await res.json();
|
const sessions = await res.json();
|
||||||
const prev = _currentSessions;
|
const prev = _currentSessions;
|
||||||
_currentSessions = sessions;
|
_currentSessions = sessions;
|
||||||
@@ -316,65 +317,11 @@ async function pollSessions() {
|
|||||||
renderSidebar(sessions, _viewingSession);
|
renderSidebar(sessions, _viewingSession);
|
||||||
handleBellTransitions(prev, sessions);
|
handleBellTransitions(prev, sessions);
|
||||||
updateSessionPill(sessions);
|
updateSessionPill(sessions);
|
||||||
} catch (err) {
|
|
||||||
_pollFailCount++;
|
|
||||||
setConnectionStatus(_pollFailCount <= 2 ? 'warn' : 'err');
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Multi-source parallel polling
|
|
||||||
const now = Date.now();
|
|
||||||
const fetchResults = await Promise.all(
|
|
||||||
_sources.map(async (source) => {
|
|
||||||
// Skip sources currently in backoff (nextRetryAt in future)
|
|
||||||
if (source.nextRetryAt && now < source.nextRetryAt) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const res = await api('GET', '/api/sessions', undefined, source.url || undefined);
|
|
||||||
const sessions = await res.json();
|
|
||||||
// Reset source status on success
|
|
||||||
source.status = 'authenticated';
|
|
||||||
source.backoffMs = 2000;
|
|
||||||
delete source.nextRetryAt;
|
|
||||||
return { source, sessions };
|
|
||||||
} catch (err) {
|
|
||||||
if (err.status === 401 || err.status === 403) {
|
|
||||||
source.status = 'auth_required';
|
|
||||||
} else {
|
|
||||||
source.status = 'unreachable';
|
|
||||||
// Exponential backoff: current * 2, capped at 30s
|
|
||||||
const newBackoff = Math.min((source.backoffMs || 2000) * 2, 30000);
|
|
||||||
source.backoffMs = newBackoff;
|
|
||||||
source.nextRetryAt = Date.now() + newBackoff;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Filter skipped/failed sources and merge results
|
|
||||||
const validResults = fetchResults.filter((r) => r !== null);
|
|
||||||
const merged = mergeSources(validResults);
|
|
||||||
|
|
||||||
// Connection status is based on local source health
|
|
||||||
const localSource = _sources.find((s) => s.type === 'local' || s.url === '');
|
|
||||||
if (localSource && localSource.status === 'authenticated') {
|
|
||||||
_pollFailCount = 0;
|
|
||||||
setConnectionStatus('ok');
|
|
||||||
} else {
|
|
||||||
_pollFailCount++;
|
|
||||||
setConnectionStatus(_pollFailCount <= 2 ? 'warn' : 'err');
|
|
||||||
}
|
|
||||||
|
|
||||||
const prev = _currentSessions;
|
|
||||||
_currentSessions = merged;
|
|
||||||
renderGrid(merged);
|
|
||||||
renderSidebar(merged, _viewingSession);
|
|
||||||
handleBellTransitions(prev, merged);
|
|
||||||
updateSessionPill(merged);
|
|
||||||
updateFaviconBadge();
|
updateFaviconBadge();
|
||||||
|
} catch (err) {
|
||||||
|
_pollFailCount++;
|
||||||
|
setConnectionStatus(_pollFailCount <= 2 ? 'warn' : 'err');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -375,6 +375,70 @@ test('pollSessions calls renderSidebar when viewMode is fullscreen', async () =>
|
|||||||
app._setViewMode('grid');
|
app._setViewMode('grid');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- pollSessions federation endpoint ---
|
||||||
|
|
||||||
|
test('pollSessions source includes /api/federation/sessions', () => {
|
||||||
|
assert.ok(
|
||||||
|
app.pollSessions.toString().includes('/api/federation/sessions'),
|
||||||
|
'pollSessions must reference /api/federation/sessions endpoint',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('pollSessions source includes multi_device_enabled check', () => {
|
||||||
|
assert.ok(
|
||||||
|
app.pollSessions.toString().includes('multi_device_enabled'),
|
||||||
|
'pollSessions must check multi_device_enabled flag',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('pollSessions uses /api/federation/sessions when multi_device_enabled is true', async () => {
|
||||||
|
const fetchedUrls = [];
|
||||||
|
const mockEl = { textContent: '', className: '' };
|
||||||
|
const origGetById = globalThis.document.getElementById;
|
||||||
|
globalThis.document.getElementById = (id) => (id === 'connection-status' ? mockEl : null);
|
||||||
|
globalThis.fetch = async (url) => {
|
||||||
|
fetchedUrls.push(url);
|
||||||
|
return { ok: true, json: async () => [] };
|
||||||
|
};
|
||||||
|
|
||||||
|
app._setServerSettings({ multi_device_enabled: true });
|
||||||
|
await app.pollSessions();
|
||||||
|
app._setServerSettings(null);
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
fetchedUrls.some((u) => u === '/api/federation/sessions'),
|
||||||
|
'should fetch /api/federation/sessions when multi_device_enabled is true',
|
||||||
|
);
|
||||||
|
globalThis.document.getElementById = origGetById;
|
||||||
|
globalThis.fetch = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('pollSessions uses /api/sessions when multi_device_enabled is false', async () => {
|
||||||
|
const fetchedUrls = [];
|
||||||
|
const mockEl = { textContent: '', className: '' };
|
||||||
|
const origGetById = globalThis.document.getElementById;
|
||||||
|
globalThis.document.getElementById = (id) => (id === 'connection-status' ? mockEl : null);
|
||||||
|
globalThis.fetch = async (url) => {
|
||||||
|
fetchedUrls.push(url);
|
||||||
|
return { ok: true, json: async () => [] };
|
||||||
|
};
|
||||||
|
|
||||||
|
app._setServerSettings({ multi_device_enabled: false });
|
||||||
|
await app.pollSessions();
|
||||||
|
app._setServerSettings(null);
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
fetchedUrls.some((u) => u === '/api/sessions'),
|
||||||
|
'should fetch /api/sessions when multi_device_enabled is false',
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
!fetchedUrls.some((u) => u === '/api/federation/sessions'),
|
||||||
|
'should NOT fetch /api/federation/sessions when multi_device_enabled is false',
|
||||||
|
);
|
||||||
|
globalThis.document.getElementById = origGetById;
|
||||||
|
globalThis.fetch = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
// --- startPolling ---
|
// --- startPolling ---
|
||||||
|
|
||||||
test('startPolling guards against double-start (only creates one interval)', () => {
|
test('startPolling guards against double-start (only creates one interval)', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user