Fix Beds24 token race condition.
Deduplicate in-flight /authentication/token requests per house so that concurrent page loads do not each trigger a separate token refresh, which was exhausting the Beds24 rate limit on first load. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent
2b24334bfa
commit
74592a9e14
+31
-16
@@ -64,6 +64,7 @@ function getRefreshToken(houseId: string): string | undefined {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const accessTokenCache = new Map<string, { token: string; expiresAt: number }>();
|
const accessTokenCache = new Map<string, { token: string; expiresAt: number }>();
|
||||||
|
const pendingTokenRequests = new Map<string, Promise<string>>();
|
||||||
|
|
||||||
async function getAccessToken(houseId: string): Promise<string> {
|
async function getAccessToken(houseId: string): Promise<string> {
|
||||||
const refreshToken = getRefreshToken(houseId);
|
const refreshToken = getRefreshToken(houseId);
|
||||||
@@ -78,26 +79,40 @@ async function getAccessToken(houseId: string): Promise<string> {
|
|||||||
return cached.token;
|
return cached.token;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(`${BEDS24_API_BASE}/authentication/token`, {
|
const inFlight = pendingTokenRequests.get(houseId);
|
||||||
method: "GET",
|
if (inFlight) {
|
||||||
headers: { refreshToken },
|
return inFlight;
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(
|
|
||||||
`Beds24 authentication error for house ${houseId}: ${response.status} ${response.statusText}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const data: { token: string; expiresIn: number } = await response.json();
|
const tokenPromise = (async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${BEDS24_API_BASE}/authentication/token`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: { refreshToken },
|
||||||
|
});
|
||||||
|
|
||||||
// Refresh a minute early to avoid using a token that expires mid-request.
|
if (!response.ok) {
|
||||||
accessTokenCache.set(houseId, {
|
throw new Error(
|
||||||
token: data.token,
|
`Beds24 authentication error for house ${houseId}: ${response.status} ${response.statusText}`
|
||||||
expiresAt: Date.now() + Math.max(data.expiresIn - 60, 30) * 1000,
|
);
|
||||||
});
|
}
|
||||||
|
|
||||||
return data.token;
|
const data: { token: string; expiresIn: number } = await response.json();
|
||||||
|
|
||||||
|
// Refresh a minute early to avoid using a token that expires mid-request.
|
||||||
|
accessTokenCache.set(houseId, {
|
||||||
|
token: data.token,
|
||||||
|
expiresAt: Date.now() + Math.max(data.expiresIn - 60, 30) * 1000,
|
||||||
|
});
|
||||||
|
|
||||||
|
return data.token;
|
||||||
|
} finally {
|
||||||
|
pendingTokenRequests.delete(houseId);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
pendingTokenRequests.set(houseId, tokenPromise);
|
||||||
|
return tokenPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function beds24Get<T>(
|
async function beds24Get<T>(
|
||||||
|
|||||||
Reference in New Issue
Block a user