/** Cache server info by account ID to avoid repeated API calls.
* Size-capped to prevent unbounded growth (#4948). */ const MAX_SERVER_INFO_CACHE_SIZE = 64; const serverInfoCache = new Map<string, { info: BlueBubblesServerInfo; expires: number }>(); const CACHE_TTL_MS = 10 * 60 * 1000; // 10 minutes
/** * Fetch server info from BlueBubbles API and cache it. * Returns cached result if available and not expired.
*/
export async function fetchBlueBubblesServerInfo(params: {
baseUrl?: string | null;
password?: string | null;
accountId?: string;
timeoutMs?: number;
allowPrivateNetwork?: boolean;
}): Promise<BlueBubblesServerInfo | null> { const baseUrl = normalizeSecretInputString(params.baseUrl); const password = normalizeSecretInputString(params.password); if (!baseUrl || !password) { returnnull;
}
const client = createBlueBubblesClientFromParts({
baseUrl,
password,
allowPrivateNetwork: params.allowPrivateNetwork === true,
timeoutMs: params.timeoutMs ?? 5000,
}); try { const res = await client.getServerInfo({ timeoutMs: params.timeoutMs ?? 5000 }); if (!res.ok) { returnnull;
} const payload = (await res.json().catch(() => null)) as Record<string, unknown> | null; const data = payload?.data as BlueBubblesServerInfo | undefined; if (data) {
serverInfoCache.set(cacheKey, { info: data, expires: Date.now() + CACHE_TTL_MS }); // Evict oldest entries if cache exceeds max size if (serverInfoCache.size > MAX_SERVER_INFO_CACHE_SIZE) { const oldest = serverInfoCache.keys().next().value; if (oldest !== undefined) {
serverInfoCache.delete(oldest);
}
}
} return data ?? null;
} catch { returnnull;
}
}
/** * Get cached server info synchronously (for use in describeMessageTool). * Returns null if not cached or expired.
*/
export function getCachedBlueBubblesServerInfo(accountId?: string): BlueBubblesServerInfo | null { const cacheKey = normalizeOptionalString(accountId) || "default"; const cached = serverInfoCache.get(cacheKey); if (cached && cached.expires > Date.now()) { return cached.info;
} returnnull;
}
/** * Read cached private API capability for a BlueBubbles account. * Returns null when capability is unknown (for example, before first probe).
*/
export function getCachedBlueBubblesPrivateApiStatus(accountId?: string): boolean | null { const info = getCachedBlueBubblesServerInfo(accountId); if (!info || typeof info.private_api !== "boolean") { returnnull;
} return info.private_api;
}
export function isBlueBubblesPrivateApiStatusEnabled(status: boolean | null): boolean { return status === true;
}
export function isBlueBubblesPrivateApiEnabled(accountId?: string): boolean { return isBlueBubblesPrivateApiStatusEnabled(getCachedBlueBubblesPrivateApiStatus(accountId));
}
/** * Parse macOS version string (e.g., "15.0.1" or "26.0") into major version number.
*/
export function parseMacOSMajorVersion(version?: string | null): number | null { if (!version) { returnnull;
} const match = /^(\d+)/.exec(version.trim()); return match ? Number.parseInt(match[1], 10) : null;
}
/** * Check if the cached server info indicates macOS 26 or higher. * Returns false if no cached info is available (fail open for action listing).
*/
export function isMacOS26OrHigher(accountId?: string): boolean { const info = getCachedBlueBubblesServerInfo(accountId); if (!info?.os_version) { returnfalse;
} const major = parseMacOSMajorVersion(info.os_version); return major !== null && major >= 26;
}
/** Clear the server info cache (for testing) */
export function clearServerInfoCache(): void {
serverInfoCache.clear();
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.