Resi Cues 1.0.2: a failed Resi login holds off instead of retrying every second
A failed login left no session, so the next 1s poll signed in again, and the next, for ever. Resi answers that with 429s, and since the retries never let up the account never got out of them: at North Attleboro the decoder picker stayed empty with "Resi login failed (429)". The picker made it worse by building a fresh client -- and signing in -- on every admin page load. - After a failed login, further attempts wait 15s, doubling to 5min (or as long as Resi's Retry-After asks); calls in between fail at once with the reason and how long until the next try. - The status says what happened: rate-limited, password rejected, or Resi unreachable. - The decoder picker reuses the running module's client when the login matches, otherwise one client per login, so it shares the hold-off. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -39,6 +39,8 @@ function msToPosition(total) {
|
||||
|
||||
const WINDOW_MS = 120000; // how long a reading stays useful
|
||||
const JUMP_FLOOR_MS = 5000; // the smallest move ever treated as a seek
|
||||
const LOGIN_BACKOFF_MS = 15000; // first wait after a failed Resi login
|
||||
const LOGIN_BACKOFF_MAX_MS = 300000; // longest wait between login attempts
|
||||
|
||||
/**
|
||||
* Resi republishes a decoder's `position` only every few seconds, on an irregular
|
||||
@@ -175,13 +177,43 @@ function createResiClient(options) {
|
||||
return h;
|
||||
}
|
||||
|
||||
/* A failed login used to be retried on the very next poll -- once a second, for
|
||||
ever. Resi answers that with 429s, and since the retries never let up, the
|
||||
account never got out of them. So a failure now holds off further attempts,
|
||||
doubling from 15s to 5min (or as long as Resi's Retry-After asks), and every
|
||||
call in between fails at once with the same reason instead of signing in. */
|
||||
const now = typeof opts.now === 'function' ? opts.now : () => Date.now();
|
||||
let loginFailures = 0;
|
||||
let loginBlockedUntil = 0;
|
||||
let loginError = '';
|
||||
|
||||
async function login() {
|
||||
const res = await doFetch(`${RESI}/api/v3/login?newToken=true`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
||||
body: JSON.stringify({ userName: opts.username, password: opts.password }),
|
||||
});
|
||||
if (!res.ok) throw new Error(`Resi login failed (${res.status})`);
|
||||
if (now() < loginBlockedUntil) {
|
||||
const wait = Math.ceil((loginBlockedUntil - now()) / 1000);
|
||||
throw new Error(`${loginError} — retrying in ${wait}s`);
|
||||
}
|
||||
let res;
|
||||
try {
|
||||
res = await doFetch(`${RESI}/api/v3/login?newToken=true`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
||||
body: JSON.stringify({ userName: opts.username, password: opts.password }),
|
||||
});
|
||||
} catch (err) {
|
||||
holdOff(`Resi unreachable (${(err && err.message) || err})`, 0);
|
||||
throw new Error(loginError);
|
||||
}
|
||||
if (!res.ok) {
|
||||
const reason = res.status === 429 ? 'Resi is rate-limiting this login (429)'
|
||||
: res.status === 400 || res.status === 401 ? `Resi rejected the login (${res.status}) — check the password`
|
||||
: `Resi login failed (${res.status})`;
|
||||
const retryAfter = Number(res.headers && res.headers.get && res.headers.get('retry-after')) || 0;
|
||||
holdOff(reason, retryAfter * 1000);
|
||||
throw new Error(reason);
|
||||
}
|
||||
loginFailures = 0;
|
||||
loginBlockedUntil = 0;
|
||||
loginError = '';
|
||||
const setCookies = (res.headers.getSetCookie && res.headers.getSetCookie())
|
||||
|| (res.headers.get('set-cookie') ? [res.headers.get('set-cookie')] : []);
|
||||
const jar = {};
|
||||
@@ -200,6 +232,13 @@ function createResiClient(options) {
|
||||
return me;
|
||||
}
|
||||
|
||||
function holdOff(reason, askedMs) {
|
||||
loginFailures++;
|
||||
const backoff = Math.min(LOGIN_BACKOFF_MAX_MS, LOGIN_BACKOFF_MS * 2 ** (loginFailures - 1));
|
||||
loginBlockedUntil = now() + Math.max(backoff, askedMs || 0);
|
||||
loginError = reason;
|
||||
}
|
||||
|
||||
async function ensureSession() {
|
||||
if (!token || Date.now() - loggedInAt > 50 * 60 * 1000) await login();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user