fix(pocketbase): self-heal admin auth instead of stranding requests
beforeSend now skips its gate for the auth endpoints (no self-await deadlock), swallows a rejected authPromise and re-authenticates when the store is invalid, re-stamps Authorization after the gate so requests built mid-auth carry the fresh token, and guards startTokenRefresh against stacking intervals. A failed boot auth logs a diagnostic instead of crashing the process; recovery happens on the next request.
This commit is contained in:
@@ -19,17 +19,38 @@ class PocketBaseAdminClient {
|
||||
this.pb = new PocketBase(process.env.POCKETBASE_URL);
|
||||
|
||||
this.pb.beforeSend = async (url, options) => {
|
||||
// The auth requests themselves must skip the gate below: gating them on
|
||||
// authPromise would make them await their own completion (deadlock).
|
||||
if (!url.includes("/collections/_superusers/auth-")) {
|
||||
try {
|
||||
await this.authPromise;
|
||||
} catch {
|
||||
// Swallow: fall through to self-heal so a rejected authPromise can't strand every request.
|
||||
}
|
||||
|
||||
if (this.pb.authStore.isValid && this.isTokenExpiringSoon()) {
|
||||
if (!this.pb.authStore.isValid) {
|
||||
// Self-heal: re-auth once PocketBase is reachable again, no restart.
|
||||
this.authPromise = this.authenticate();
|
||||
await this.authPromise;
|
||||
this.startTokenRefresh();
|
||||
} else if (this.isTokenExpiringSoon()) {
|
||||
try {
|
||||
await this.refreshAuth();
|
||||
} catch (error) {
|
||||
console.error('Failed to refresh admin token, re-authenticating:', error);
|
||||
await this.authenticate();
|
||||
this.authPromise = this.authenticate();
|
||||
await this.authPromise;
|
||||
}
|
||||
}
|
||||
|
||||
// The SDK stamps Authorization before this hook runs, so a request
|
||||
// built while auth was still in flight carries no (or a stale) token.
|
||||
options.headers = {
|
||||
...options.headers,
|
||||
Authorization: this.pb.authStore.token,
|
||||
};
|
||||
}
|
||||
|
||||
options.cache = "no-store";
|
||||
options.headers = {
|
||||
...options.headers,
|
||||
@@ -54,8 +75,17 @@ class PocketBaseAdminClient {
|
||||
Object.assign(this, createPushService(this.pb));
|
||||
|
||||
this.authPromise = this.authenticate();
|
||||
this.authPromise.then(() => {
|
||||
// Fail soft at boot: an unreachable PocketBase must not crash the process;
|
||||
// beforeSend self-heals on the next request.
|
||||
this.authPromise
|
||||
.then(() => {
|
||||
this.startTokenRefresh();
|
||||
})
|
||||
.catch(() => {
|
||||
console.error(
|
||||
"PocketBase admin authentication failed - is PocketBase running and are " +
|
||||
"POCKETBASE_URL / POCKETBASE_ADMIN_EMAIL / POCKETBASE_ADMIN_PASSWORD set?"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -98,6 +128,8 @@ class PocketBaseAdminClient {
|
||||
}
|
||||
|
||||
private startTokenRefresh() {
|
||||
if (this.refreshInterval) return;
|
||||
|
||||
this.refreshInterval = setInterval(async () => {
|
||||
try {
|
||||
await this.refreshAuth();
|
||||
|
||||
Reference in New Issue
Block a user