[READ-ONLY] a fast, modern browser for the npm registry
at main 68 lines 2.2 kB view raw
1import type { RuntimeLock } from '@atproto/oauth-client-node' 2import { requestLocalLock } from '@atproto/oauth-client-node' 3import { Redis } from '@upstash/redis' 4 5type Awaitable<T> = T | PromiseLike<T> 6 7/** 8 * Creates a distributed lock using Upstash Redis. 9 * Falls back gracefully if the lock cannot be acquired. 10 */ 11function createUpstashLock(redis: Redis): RuntimeLock { 12 return async <T>(key: string, fn: () => Awaitable<T>): Promise<T> => { 13 const lockKey = `oauth:lock:${key}` 14 const lockValue = crypto.randomUUID() 15 const lockTTL = 30 // seconds 16 17 // Try to acquire lock with NX (only set if not exists) and EX (expire) 18 const acquired = await redis.set(lockKey, lockValue, { 19 nx: true, 20 ex: lockTTL, 21 }) 22 23 if (!acquired) { 24 // Another instance holds the lock, wait briefly and retry once 25 await new Promise(resolve => setTimeout(resolve, 100)) 26 const retryAcquired = await redis.set(lockKey, lockValue, { 27 nx: true, 28 ex: lockTTL, 29 }) 30 if (!retryAcquired) { 31 // Still can't acquire, proceed without lock (better than failing) 32 // The worst case is a token refresh race, which will just require re-auth 33 return await fn() 34 } 35 } 36 37 try { 38 return await fn() 39 } finally { 40 // Release lock only if we still own it (compare-and-delete) 41 const currentValue = await redis.get(lockKey) 42 if (currentValue === lockValue) { 43 await redis.del(lockKey) 44 } 45 } 46 } 47} 48 49/** 50 * Returns the appropriate lock mechanism based on environment: 51 * - Production with Upstash config: distributed Redis lock 52 * - Otherwise: in-memory lock (sufficient for single instance) 53 */ 54export function getOAuthLock(): RuntimeLock { 55 const config = useRuntimeConfig() 56 57 // Use distributed lock in production if Upstash is configured 58 if (!import.meta.dev && config.upstash?.redisRestUrl && config.upstash?.redisRestToken) { 59 const redis = new Redis({ 60 url: config.upstash.redisRestUrl, 61 token: config.upstash.redisRestToken, 62 }) 63 return createUpstashLock(redis) 64 } 65 66 // Fall back to in-memory lock for dev/preview or when Redis isn't configured 67 return requestLocalLock 68}