fix: cache API key in-process to survive keychain failures mid-session (#1192)
This commit is contained in:
@@ -15,6 +15,12 @@ type SDKDiagnostic = {
|
|||||||
|
|
||||||
let lastSDKDiagnostic: SDKDiagnostic | null = null;
|
let lastSDKDiagnostic: SDKDiagnostic | null = null;
|
||||||
|
|
||||||
|
// In-process cache of the last successfully obtained API key (not from a
|
||||||
|
// static env var). Populated on first successful keychain read and updated
|
||||||
|
// whenever the OAuth refresh obtains a new token. Used as a fallback so
|
||||||
|
// transient keychain failures don't crash the process mid-session.
|
||||||
|
let _cachedApiKey: string | undefined;
|
||||||
|
|
||||||
function safeDiagnosticString(value: unknown): string {
|
function safeDiagnosticString(value: unknown): string {
|
||||||
if (value === null || value === undefined) {
|
if (value === null || value === undefined) {
|
||||||
return "";
|
return "";
|
||||||
@@ -108,6 +114,18 @@ export async function getClient() {
|
|||||||
|
|
||||||
let apiKey = process.env.LETTA_API_KEY || settings.env?.LETTA_API_KEY;
|
let apiKey = process.env.LETTA_API_KEY || settings.env?.LETTA_API_KEY;
|
||||||
|
|
||||||
|
if (!process.env.LETTA_API_KEY) {
|
||||||
|
if (apiKey) {
|
||||||
|
// Keep the in-process cache current on every successful keychain read.
|
||||||
|
_cachedApiKey = apiKey;
|
||||||
|
} else if (_cachedApiKey) {
|
||||||
|
// Keychain returned null (e.g. delete-then-set race during token
|
||||||
|
// rotation, or a transient keychain failure). Fall back to the last
|
||||||
|
// key we successfully obtained so the process doesn't crash mid-session.
|
||||||
|
apiKey = _cachedApiKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check if token is expired and refresh if needed
|
// Check if token is expired and refresh if needed
|
||||||
if (
|
if (
|
||||||
!process.env.LETTA_API_KEY &&
|
!process.env.LETTA_API_KEY &&
|
||||||
@@ -117,8 +135,10 @@ export async function getClient() {
|
|||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const expiresAt = settings.tokenExpiresAt;
|
const expiresAt = settings.tokenExpiresAt;
|
||||||
|
|
||||||
// Refresh if token expires within 5 minutes
|
// Refresh if token expires within 5 minutes, or if the access token is
|
||||||
if (expiresAt - now < 5 * 60 * 1000) {
|
// missing entirely (e.g. transient keychain read failure during the
|
||||||
|
// delete-then-set window of a concurrent refresh).
|
||||||
|
if (!apiKey || expiresAt - now < 5 * 60 * 1000) {
|
||||||
try {
|
try {
|
||||||
// Get or generate device ID (should always exist, but fallback just in case)
|
// Get or generate device ID (should always exist, but fallback just in case)
|
||||||
const deviceId = settingsManager.getOrCreateDeviceId();
|
const deviceId = settingsManager.getOrCreateDeviceId();
|
||||||
@@ -138,6 +158,7 @@ export async function getClient() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
apiKey = tokens.access_token;
|
apiKey = tokens.access_token;
|
||||||
|
_cachedApiKey = tokens.access_token;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to refresh access token:", error);
|
console.error("Failed to refresh access token:", error);
|
||||||
console.error("Please run 'letta login' to re-authenticate");
|
console.error("Please run 'letta login' to re-authenticate");
|
||||||
|
|||||||
Reference in New Issue
Block a user