codexmobile-relay / scripts /relay-mac-client-keepalive.mjs
Codex
deploy: CodexMobile Relay
90f0300
Raw
History Blame Contribute Delete
2.65 kB
export const DEFAULT_RELAY_KEEPALIVE_MS = 240000;
export const MIN_RELAY_KEEPALIVE_MS = 60000;
const DEFAULT_KEEPALIVE_PATH = '/api/status';
const KEEPALIVE_TIMEOUT_MS = 10000;
export function parseRelayKeepaliveMs(value, fallback = DEFAULT_RELAY_KEEPALIVE_MS) {
if (String(value).trim() === '0') {
return 0;
}
const next = Number(value);
if (!Number.isFinite(next) || next < MIN_RELAY_KEEPALIVE_MS) {
return fallback;
}
return Math.floor(next);
}
export function buildRelayKeepaliveUrl(relayUrl, pathname = DEFAULT_KEEPALIVE_PATH) {
const url = new URL(relayUrl);
if (url.username || url.password) {
throw new Error('relay_keepalive_invalid_relay_url');
}
if (url.protocol === 'wss:') {
url.protocol = 'https:';
} else if (url.protocol === 'ws:') {
url.protocol = 'http:';
} else {
throw new Error('relay_keepalive_invalid_relay_url');
}
url.pathname = pathname;
url.search = '';
url.hash = '';
url.searchParams.set('keepalive', '1');
return url.toString();
}
export function createRelayKeepalive({
relayUrl,
intervalMs = DEFAULT_RELAY_KEEPALIVE_MS,
fetchImpl = globalThis.fetch,
logState = () => {},
setIntervalFn = setInterval,
clearIntervalFn = clearInterval
}) {
const keepaliveUrl = buildRelayKeepaliveUrl(relayUrl);
let timer = null;
let pingInFlight = null;
async function ping() {
if (pingInFlight) {
return pingInFlight;
}
pingInFlight = runPing().finally(() => {
pingInFlight = null;
});
return pingInFlight;
}
async function runPing() {
try {
const response = await fetchImpl(keepaliveUrl, {
method: 'GET',
headers: {
accept: 'application/json',
'cache-control': 'no-store'
},
signal: AbortSignal.timeout(KEEPALIVE_TIMEOUT_MS)
});
await releaseResponseBody(response);
if (!response.ok) {
logState('keepalive_failed', `status=${response.status}`);
return false;
}
logState('keepalive', `status=${response.status}`);
return true;
} catch (error) {
logState('keepalive_failed', error.message || 'request_failed');
return false;
}
}
function start() {
if (!intervalMs || timer) {
return;
}
timer = setIntervalFn(() => ping(), intervalMs);
timer.unref?.();
}
function stop() {
if (!timer) {
return;
}
clearIntervalFn(timer);
timer = null;
}
async function releaseResponseBody(response) {
try {
await response.body?.cancel?.();
} catch {
return;
}
}
return {
ping,
start,
stop
};
}