Erster Commit
This commit is contained in:
+107
@@ -0,0 +1,107 @@
|
||||
const FAILOVER_MAX_ATTEMPTS = 3;
|
||||
const FAILOVER_BACKOFF_BASE_MS = 200;
|
||||
const MIN_FAILOVER_TIMEOUT_SECONDS = 5;
|
||||
function failoverAttempts(enabled, hostname, force = false, timeoutSeconds = 0) {
|
||||
if (!enabled || !(force || isCloud(hostname))) {
|
||||
return 1;
|
||||
}
|
||||
if (timeoutSeconds > 0 && timeoutSeconds < MIN_FAILOVER_TIMEOUT_SECONDS) {
|
||||
return 1;
|
||||
}
|
||||
return FAILOVER_MAX_ATTEMPTS;
|
||||
}
|
||||
function isCloud(hostname) {
|
||||
return hostname.endsWith(".livekit.cloud");
|
||||
}
|
||||
function toHttp(url) {
|
||||
return url.startsWith("ws") ? `http${url.slice(2)}` : url;
|
||||
}
|
||||
function hostKey(url) {
|
||||
return url.host.toLowerCase();
|
||||
}
|
||||
function pickNext(regionOrigins2, attempted) {
|
||||
for (const origin of regionOrigins2) {
|
||||
try {
|
||||
if (!attempted.has(hostKey(new URL(origin)))) {
|
||||
return origin;
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
return void 0;
|
||||
}
|
||||
function sleep(ms) {
|
||||
return ms > 0 ? new Promise((resolve) => setTimeout(resolve, ms)) : Promise.resolve();
|
||||
}
|
||||
const regionCache = /* @__PURE__ */ new Map();
|
||||
const inflight = /* @__PURE__ */ new Map();
|
||||
async function regionOrigins(origin, headers) {
|
||||
const key = hostKey(origin);
|
||||
const cached = regionCache.get(key);
|
||||
if (cached && Date.now() - cached.fetchedAt < cached.ttl) {
|
||||
return cached.origins;
|
||||
}
|
||||
const existing = inflight.get(key);
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
const request = (async () => {
|
||||
try {
|
||||
const { origins, ttl } = await fetchRegions(origin, headers);
|
||||
if (ttl > 0) {
|
||||
regionCache.set(key, { origins, fetchedAt: Date.now(), ttl });
|
||||
}
|
||||
return origins;
|
||||
} catch {
|
||||
return (cached == null ? void 0 : cached.origins) ?? [];
|
||||
} finally {
|
||||
inflight.delete(key);
|
||||
}
|
||||
})();
|
||||
inflight.set(key, request);
|
||||
return request;
|
||||
}
|
||||
async function fetchRegions(origin, headers) {
|
||||
const fetchHeaders = {};
|
||||
for (const [k, v] of Object.entries(headers ?? {})) {
|
||||
if (k.toLowerCase() === "content-type" || k.toLowerCase() === "content-length") continue;
|
||||
fetchHeaders[k] = v;
|
||||
}
|
||||
const response = await fetch(new URL("/settings/regions", origin.origin), {
|
||||
method: "GET",
|
||||
headers: fetchHeaders,
|
||||
// Short timeout so a slow/unreachable discovery endpoint doesn't stall the
|
||||
// failover path.
|
||||
signal: AbortSignal.timeout(2e3)
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`region discovery failed: ${response.status}`);
|
||||
}
|
||||
const ttl = parseMaxAge(response.headers.get("cache-control"));
|
||||
const body = await response.json();
|
||||
const origins = (body.regions ?? []).filter((r) => !!r.url).map((r) => new URL(toHttp(r.url)).origin);
|
||||
return { origins, ttl };
|
||||
}
|
||||
function parseMaxAge(cacheControl) {
|
||||
if (!cacheControl) return 0;
|
||||
for (const directive of cacheControl.split(",")) {
|
||||
const trimmed = directive.trim().toLowerCase();
|
||||
if (trimmed.startsWith("max-age=")) {
|
||||
const secs = parseInt(trimmed.slice("max-age=".length), 10);
|
||||
return Number.isFinite(secs) && secs > 0 ? secs * 1e3 : 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
export {
|
||||
FAILOVER_BACKOFF_BASE_MS,
|
||||
FAILOVER_MAX_ATTEMPTS,
|
||||
MIN_FAILOVER_TIMEOUT_SECONDS,
|
||||
failoverAttempts,
|
||||
hostKey,
|
||||
parseMaxAge,
|
||||
pickNext,
|
||||
regionOrigins,
|
||||
sleep
|
||||
};
|
||||
//# sourceMappingURL=failover.js.map
|
||||
Reference in New Issue
Block a user