@tanstack/offline-transactions
Version:
Offline-first transaction capabilities for TanStack DB
89 lines (76 loc) • 2.26 kB
text/typescript
import { BaseLeaderElection } from './LeaderElection'
export class WebLocksLeader extends BaseLeaderElection {
private lockName: string
private releaseLock: (() => void) | null = null
constructor(lockName = `offline-executor-leader`) {
super()
this.lockName = lockName
}
async requestLeadership(): Promise<boolean> {
if (!this.isWebLocksSupported()) {
return false
}
if (this.isLeaderState) {
return true
}
try {
// First try to acquire the lock with ifAvailable
const available = await navigator.locks.request(
this.lockName,
{
mode: `exclusive`,
ifAvailable: true,
},
(lock) => {
return lock !== null
},
)
if (!available) {
return false
}
// Set state immediately to prevent duplicate notifications
// when the async lock acquisition calls notifyLeadershipChange(true).
// The guard in notifyLeadershipChange checks `isLeaderState !== isLeader`,
// so setting this to true here prevents the callback from firing again.
this.isLeaderState = true
// Lock is available, now acquire it for real and hold it
navigator.locks.request(
this.lockName,
{
mode: `exclusive`,
},
async (lock) => {
if (lock) {
this.notifyLeadershipChange(true)
// Hold the lock until released
return new Promise<void>((resolve) => {
this.releaseLock = () => {
this.notifyLeadershipChange(false)
resolve()
}
})
}
},
)
return true
} catch (error) {
if (error instanceof Error && error.name === `AbortError`) {
return false
}
console.warn(`Web Locks leadership request failed:`, error)
return false
}
}
releaseLeadership(): void {
if (this.releaseLock) {
this.releaseLock()
this.releaseLock = null
}
}
private isWebLocksSupported(): boolean {
return typeof navigator !== `undefined` && `locks` in navigator
}
static isSupported(): boolean {
return typeof navigator !== `undefined` && `locks` in navigator
}
}