45 lines
1.4 KiB
TypeScript
45 lines
1.4 KiB
TypeScript
export interface PiProviderAuthRecoveryOptions<T> {
|
|
accountId: string;
|
|
operation: (attempt: 0 | 1) => Promise<T>;
|
|
isAuthenticationError: (error: unknown) => boolean;
|
|
refreshCredential: () => Promise<void>;
|
|
reopenWorker: () => Promise<void>;
|
|
}
|
|
|
|
export class PiProviderRefreshCoordinator {
|
|
private readonly refreshes = new Map<string, Promise<void>>();
|
|
|
|
get pendingAccountCount(): number {
|
|
return this.refreshes.size;
|
|
}
|
|
|
|
async refreshAccount(accountId: string, refresh: () => Promise<void>): Promise<void> {
|
|
const normalizedAccountId = accountId.trim();
|
|
if (!normalizedAccountId) throw new Error('Provider account id is required');
|
|
let pending = this.refreshes.get(normalizedAccountId);
|
|
if (!pending) {
|
|
pending = Promise.resolve()
|
|
.then(refresh)
|
|
.finally(() => {
|
|
if (this.refreshes.get(normalizedAccountId) === pending) {
|
|
this.refreshes.delete(normalizedAccountId);
|
|
}
|
|
});
|
|
this.refreshes.set(normalizedAccountId, pending);
|
|
}
|
|
await pending;
|
|
}
|
|
|
|
async withSingleAuthRecovery<T>(options: PiProviderAuthRecoveryOptions<T>): Promise<T> {
|
|
try {
|
|
return await options.operation(0);
|
|
} catch (error) {
|
|
if (!options.isAuthenticationError(error)) throw error;
|
|
}
|
|
|
|
await this.refreshAccount(options.accountId, options.refreshCredential);
|
|
await options.reopenWorker();
|
|
return await options.operation(1);
|
|
}
|
|
}
|